diff --git a/package.json b/package.json
new file mode 100644
index 000000000..9722bc132
--- /dev/null
+++ b/package.json
@@ -0,0 +1,38 @@
+{
+ "name": "saaslabs",
+ "version": "0.1.0",
+ "private": true,
+ "dependencies": {
+ "cra-template": "1.2.0",
+ "react": "^19.0.0",
+ "react-dom": "^19.0.0",
+ "react-scripts": "5.0.1"
+ },
+ "scripts": {
+ "start": "react-scripts start",
+ "build": "react-scripts build",
+ "test": "react-scripts test",
+ "eject": "react-scripts eject"
+ },
+ "eslintConfig": {
+ "extends": [
+ "react-app",
+ "react-app/jest"
+ ]
+ },
+ "browserslist": {
+ "production": [
+ ">0.2%",
+ "not dead",
+ "not op_mini all"
+ ],
+ "development": [
+ "last 1 chrome version",
+ "last 1 firefox version",
+ "last 1 safari version"
+ ]
+ },
+ "devDependencies": {
+ "web-vitals": "^4.2.4"
+ }
+}
diff --git a/public/favicon.ico b/public/favicon.ico
new file mode 100644
index 000000000..a11777cc4
Binary files /dev/null and b/public/favicon.ico differ
diff --git a/public/index.html b/public/index.html
new file mode 100644
index 000000000..aa069f27c
--- /dev/null
+++ b/public/index.html
@@ -0,0 +1,43 @@
+
+
+
+
+
+
+
+
+
+
+
+
+ React App
+
+
+
+
+
+
+
diff --git a/public/logo192.png b/public/logo192.png
new file mode 100644
index 000000000..fc44b0a37
Binary files /dev/null and b/public/logo192.png differ
diff --git a/public/logo512.png b/public/logo512.png
new file mode 100644
index 000000000..a4e47a654
Binary files /dev/null and b/public/logo512.png differ
diff --git a/public/manifest.json b/public/manifest.json
new file mode 100644
index 000000000..080d6c77a
--- /dev/null
+++ b/public/manifest.json
@@ -0,0 +1,25 @@
+{
+ "short_name": "React App",
+ "name": "Create React App Sample",
+ "icons": [
+ {
+ "src": "favicon.ico",
+ "sizes": "64x64 32x32 24x24 16x16",
+ "type": "image/x-icon"
+ },
+ {
+ "src": "logo192.png",
+ "type": "image/png",
+ "sizes": "192x192"
+ },
+ {
+ "src": "logo512.png",
+ "type": "image/png",
+ "sizes": "512x512"
+ }
+ ],
+ "start_url": ".",
+ "display": "standalone",
+ "theme_color": "#000000",
+ "background_color": "#ffffff"
+}
diff --git a/public/robots.txt b/public/robots.txt
new file mode 100644
index 000000000..e9e57dc4d
--- /dev/null
+++ b/public/robots.txt
@@ -0,0 +1,3 @@
+# https://www.robotstxt.org/robotstxt.html
+User-agent: *
+Disallow:
diff --git a/src/App.css b/src/App.css
new file mode 100644
index 000000000..74b5e0534
--- /dev/null
+++ b/src/App.css
@@ -0,0 +1,38 @@
+.App {
+ text-align: center;
+}
+
+.App-logo {
+ height: 40vmin;
+ pointer-events: none;
+}
+
+@media (prefers-reduced-motion: no-preference) {
+ .App-logo {
+ animation: App-logo-spin infinite 20s linear;
+ }
+}
+
+.App-header {
+ background-color: #282c34;
+ min-height: 100vh;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ font-size: calc(10px + 2vmin);
+ color: white;
+}
+
+.App-link {
+ color: #61dafb;
+}
+
+@keyframes App-logo-spin {
+ from {
+ transform: rotate(0deg);
+ }
+ to {
+ transform: rotate(360deg);
+ }
+}
diff --git a/src/App.js b/src/App.js
new file mode 100644
index 000000000..ebfb9caab
--- /dev/null
+++ b/src/App.js
@@ -0,0 +1,73 @@
+
+import './App.css';
+import Table from './components/table/index.tsx';
+import {theadData} from './mockData/mockData.js';
+import {useEffect, useState} from 'react';
+import { getService } from './services/http.ts';
+import {returnDataWrtTableContent} from './utils/helpers.ts';
+
+
+function App() {
+
+ // console.log(data);
+ const [tbodyData, settbodyData] = useState([]);
+ const [startIndex, setStartIndex] = useState(0);
+ const [endIndex, setendIndex] = useState(5);
+ const [loading, setLoading] = useState(false);
+ const [tableData, setTbaldata] = useState([]);
+
+ const callAPI = async ()=>{
+ setLoading(true);
+ const resp = await getService('https://raw.githubusercontent.com/saaslabsco/frontend-assignment/refs/heads/master/frontend-assignment.json');
+ const {data, pageSize} = returnDataWrtTableContent(resp);
+ settbodyData([...data]);
+ setLoading(false);
+ setTbaldata([...resp])
+ }
+
+ useEffect(()=>{
+ callAPI();
+ },[])
+
+ const handlePrevPage =()=>{
+ setStartIndex(startIndex - 5);
+ setendIndex(endIndex - 5);
+ }
+
+ const handleNextPage =()=>{
+ setStartIndex(endIndex);
+ setendIndex(endIndex + 5);
+ }
+
+ useEffect(()=>{
+ const data = returnDataWrtTableContent(tableData, startIndex, endIndex);
+ settbodyData([...data.data]);
+ console.log(startIndex, endIndex);
+
+ },[startIndex, endIndex])
+
+ function Pagination (){
+
+ return (
+
+
+
+
+ )
+ }
+
+
+ return (
+
+
SaasLabs Assignment
+
+
+
+ );
+}
+
+export default App;
diff --git a/src/components/table/index.css b/src/components/table/index.css
new file mode 100644
index 000000000..daf98d557
--- /dev/null
+++ b/src/components/table/index.css
@@ -0,0 +1,20 @@
+#tbl {
+ font-family: Arial, Helvetica, sans-serif;
+ border-collapse: collapse;
+ width: 100%;
+ }
+
+
+ #tbl td, #tbl th {
+ border: 1px solid #ddd;
+ padding: 8px;
+ }
+
+
+ #tbl th {
+ padding-top: 12px;
+ padding-bottom: 12px;
+ text-align: left;
+ background-color: #04AA6D;
+ color: white;
+ }
\ No newline at end of file
diff --git a/src/components/table/index.tsx b/src/components/table/index.tsx
new file mode 100644
index 000000000..fa6e60842
--- /dev/null
+++ b/src/components/table/index.tsx
@@ -0,0 +1,51 @@
+import React from "react";
+import TableRow from "./tableRow/index.tsx";
+import TableHeader from "./tableHeader/index.tsx";
+import './index.css'
+
+type tbodyType = {
+ "s.no": number;
+ "amt.pledged": number;
+ blurb: string;
+ by: string;
+ country: string;
+ currency: string;
+ "end.time": string;
+ location: string;
+ "percentage.funded": number;
+ "num.backers": string;
+ state: string;
+ title: string;
+ type: string;
+ url: string;
+};
+
+interface tableProps {
+ theadData: string[];
+ tbodyData: tbodyType[];
+ loading: boolean
+}
+
+const Table = (props: tableProps) => {
+ const { theadData = [], tbodyData = [], loading } = props;
+ return (
+
+
+
+ {theadData.map((h) => {
+ return ;
+ })}
+
+
+
+ {
+ loading ? Loading ...
: tbodyData.map((item) => {
+ return ;
+ })}
+
+
+
+ );
+};
+
+export default Table;
diff --git a/src/components/table/tableHeader/index.test.js b/src/components/table/tableHeader/index.test.js
new file mode 100644
index 000000000..fa5efa53e
--- /dev/null
+++ b/src/components/table/tableHeader/index.test.js
@@ -0,0 +1,8 @@
+import { render, screen } from '@testing-library/react';
+import TableHeader from './index.tsx';
+
+test('render table header with correct title', () => {
+ render();
+ const textEle = screen.getByText(/S.No./i);
+ expect(textEle).toBeInTheDocument();
+});
diff --git a/src/components/table/tableHeader/index.tsx b/src/components/table/tableHeader/index.tsx
new file mode 100644
index 000000000..208e900e5
--- /dev/null
+++ b/src/components/table/tableHeader/index.tsx
@@ -0,0 +1,11 @@
+import React from "react";
+
+const TableHeader = ({ item }) => {
+ return (
+
+ {item}
+ |
+ );
+};
+
+export default TableHeader;
\ No newline at end of file
diff --git a/src/components/table/tableRow/index.test.js b/src/components/table/tableRow/index.test.js
new file mode 100644
index 000000000..bf297e5f0
--- /dev/null
+++ b/src/components/table/tableRow/index.test.js
@@ -0,0 +1,9 @@
+import { render, screen } from '@testing-library/react';
+import TableRow from './index.tsx';
+
+test('render table body with correct title', () => {
+ const mock = [1,2,3]
+ render();
+ const textEle = screen.getByText(/1/i);
+ expect(textEle).toBeInTheDocument();
+});
diff --git a/src/components/table/tableRow/index.tsx b/src/components/table/tableRow/index.tsx
new file mode 100644
index 000000000..a632bc664
--- /dev/null
+++ b/src/components/table/tableRow/index.tsx
@@ -0,0 +1,28 @@
+
+import React from "react";
+
+type dataObj = {
+ id: number,
+ items: number[]
+}
+
+interface props{
+ data: dataObj,
+ loading: boolean,
+}
+
+const TableRow = (props: props) => {
+ const { data = [] } = props;
+ return (
+ <>
+
+ {data?.map((item) => {
+ return | {item} | ;
+ })}
+
+
+ >
+ );
+};
+
+export default TableRow;
\ No newline at end of file
diff --git a/src/index.css b/src/index.css
new file mode 100644
index 000000000..ec2585e8c
--- /dev/null
+++ b/src/index.css
@@ -0,0 +1,13 @@
+body {
+ margin: 0;
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
+ 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
+ sans-serif;
+ -webkit-font-smoothing: antialiased;
+ -moz-osx-font-smoothing: grayscale;
+}
+
+code {
+ font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
+ monospace;
+}
diff --git a/src/index.js b/src/index.js
new file mode 100644
index 000000000..d563c0fb1
--- /dev/null
+++ b/src/index.js
@@ -0,0 +1,17 @@
+import React from 'react';
+import ReactDOM from 'react-dom/client';
+import './index.css';
+import App from './App';
+import reportWebVitals from './reportWebVitals';
+
+const root = ReactDOM.createRoot(document.getElementById('root'));
+root.render(
+
+
+
+);
+
+// If you want to start measuring performance in your app, pass a function
+// to log results (for example: reportWebVitals(console.log))
+// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
+reportWebVitals();
diff --git a/src/logo.svg b/src/logo.svg
new file mode 100644
index 000000000..9dfc1c058
--- /dev/null
+++ b/src/logo.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/mockData/mockData.js b/src/mockData/mockData.js
new file mode 100644
index 000000000..be06fe8da
--- /dev/null
+++ b/src/mockData/mockData.js
@@ -0,0 +1,5 @@
+const theadData = ["S.No.", "Percentage funded", "Amount pledged"];
+
+export {
+ theadData,
+}
\ No newline at end of file
diff --git a/src/reportWebVitals.js b/src/reportWebVitals.js
new file mode 100644
index 000000000..5253d3ad9
--- /dev/null
+++ b/src/reportWebVitals.js
@@ -0,0 +1,13 @@
+const reportWebVitals = onPerfEntry => {
+ if (onPerfEntry && onPerfEntry instanceof Function) {
+ import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {
+ getCLS(onPerfEntry);
+ getFID(onPerfEntry);
+ getFCP(onPerfEntry);
+ getLCP(onPerfEntry);
+ getTTFB(onPerfEntry);
+ });
+ }
+};
+
+export default reportWebVitals;
diff --git a/src/services/http.ts b/src/services/http.ts
new file mode 100644
index 000000000..dfa69d89f
--- /dev/null
+++ b/src/services/http.ts
@@ -0,0 +1,17 @@
+
+const getService = async (baseURL: string) =>{
+ try{
+ const response = await fetch(baseURL, {
+ method: 'GET'
+ })
+ if(response.status > 400){
+ return [];
+ }
+ return await response.json();
+ }
+ catch(err){
+ console.log('in catch', err);
+ }
+}
+
+export {getService}
\ No newline at end of file
diff --git a/src/setupTests.js b/src/setupTests.js
new file mode 100644
index 000000000..8f2609b7b
--- /dev/null
+++ b/src/setupTests.js
@@ -0,0 +1,5 @@
+// jest-dom adds custom jest matchers for asserting on DOM nodes.
+// allows you to do things like:
+// expect(element).toHaveTextContent(/react/i)
+// learn more: https://github.com/testing-library/jest-dom
+import '@testing-library/jest-dom';
diff --git a/src/utils/helpers.ts b/src/utils/helpers.ts
new file mode 100644
index 000000000..c4f1d18b1
--- /dev/null
+++ b/src/utils/helpers.ts
@@ -0,0 +1,21 @@
+
+type inputType = {};
+type outputType = {
+ data: inputType[],
+ pageSize: number,
+}
+function returnDataWrtTableContent(data: inputType[] = [], startIndex: number = 0, endIndex: number = 5): outputType{
+ // just extract only keys which we need to have for showing data ie, sl no, ...
+ const tdataList = data?.map((item, index)=>{
+ let obj: {id: number, items: string[]} = {};
+ obj.id = item["s.no"];
+ obj.items = [index+1, item["amt.pledged"], item["percentage.funded"]];
+ return obj;
+ })
+ return {
+ data: tdataList.slice(startIndex, endIndex) ?? [],
+ pageSize: data.length/5
+ };
+}
+
+export {returnDataWrtTableContent}
\ No newline at end of file