Skip to content
Merged
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
3 changes: 3 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
VITE_ROWS_API_KEY=
VITE_SPREADSHEET_ID=
VITE_TABLE_ID=
18 changes: 17 additions & 1 deletion .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,20 @@ name: Deploy to Google Store
on: workflow_dispatch

jobs:
create-envfile:
name: Creating the .env file
runs-on: ubuntu-latest

steps:
- name: Make envfile
uses: SpicyPizza/create-envfile@v2.0
with:
envkey_VITE_ROWS_API_KEY: ${{ secrets.ROWS_API_KEY }}
envkey_VITE_SPREADSHEET_ID: ${{ secrets.SPREADSHEET_ID }}
envkey_VITE_TABLE_ID: ${{ secrets.TABLE_ID }}
file_name: .env
fail_on_empty: true

test:
name: E2E Tests
runs-on: ubuntu-latest
Expand All @@ -24,7 +38,9 @@ jobs:

build-and-deploy-extension:
name: Build & deploy extension
needs: test
needs:
- test
- create-envfile
runs-on: ubuntu-latest
timeout-minutes: 20
steps:
Expand Down
10 changes: 10 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,16 @@ Once your development environment is set up, follow these steps to start using o
> 6. Pin the extension to reach it easily :smiley:
> 7. You can make changes to your extension files, and they will automatically be reflected in the browser.

### .env File

The environment configuration file plays a crucial role in managing the application's integration with the Rows API (this is needed to store the information of user feedback, if you're not doing anywork related with that we could leave it empty in our local computer). The env file contains three specific variables essential for ensuring that the application can securely and accurately interact with the Rows API. Here’s a brief explanation of each variable:

- `VITE_ROWS_API_KEY`: This variable stores the Rows API key, which is necessary for authenticating requests made from our application to the Rows service. It ensures that our application has the permission to access and modify the spreadsheet data.
- `VITE_SPREADSHEET_ID`: This variable holds the ID of the spreadsheet we want to access. It specifies the target spreadsheet within the Rows platform where all the data from our application is stored or retrieved from.
- `VITE_TABLE_ID`: This variable contains the ID of the specific table within the spreadsheet mentioned above. It identifies the exact location within the spreadsheet where data entries should be made or updated.

By storing these keys in the environment configuration file and automating its creation during the release process via GitHub Actions, we ensure that the setup is secure, efficient, and less prone to errors, enabling seamless integration and data management.

## How to add a new scrapper?

There are 2 different ways of building a custom scrapper:
Expand Down
32 changes: 31 additions & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@
"@types/jest-environment-puppeteer": "^5.0.6",
"@types/puppeteer": "^7.0.4",
"eslint-plugin-import": "^2.29.1",
"preact": "^10.19.3"
"preact": "^10.19.3",
"ua-parser-js": "^1.0.37"
},
"devDependencies": {
"@crxjs/vite-plugin": "^2.0.0-beta.23",
Expand All @@ -27,6 +28,7 @@
"@types/chrome": "^0.0.256",
"@types/jest": "^29.5.11",
"@types/js-yaml": "^4.0.9",
"@types/ua-parser-js": "^0.7.39",
"@typescript-eslint/eslint-plugin": "^4.28.3",
"@typescript-eslint/parser": "^4.0.0",
"commitizen": "^4.3.0",
Expand Down
22 changes: 15 additions & 7 deletions src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { FunctionalComponent } from 'preact';
import { useEffect, useState } from 'preact/hooks';
import { useEffect, useState, useReducer } from 'preact/hooks';
import './index.css';
import FeedbackForm from './components/feedback-form';
import NoResults from './components/no-results';
import Header from './components/header';
import Preview from './components/preview';
Expand All @@ -15,6 +16,7 @@ const App: FunctionalComponent = () => {
const [isLoading, setLoading] = useState(true);
const [exceptionOnScrapperResult, setException] = useState("");
const [results, setResults] = useState([]);
const [isReportFormOpen, toggleReportTab] = useReducer((isOpen) => !isOpen, false);

const hasExceptions = Boolean(exceptionOnScrapperResult);
const showLoading = !hasExceptions && isLoading;
Expand All @@ -28,7 +30,7 @@ const App: FunctionalComponent = () => {
setException(response.message);
} else {
setResults(response);
setException("");
setException('');
}

setLoading(false);
Expand All @@ -37,12 +39,18 @@ const App: FunctionalComponent = () => {

return (
<>
<Header />
<Header onReportClick={toggleReportTab} />
<div className="container">
{showLoading && (<LoadingSkeleton />)}
{showResults && <Preview results={results} />}
{noResults && <NoResults />}
{hasExceptions && <NoResults message={exceptionOnScrapperResult} />}
{isReportFormOpen ? (
<FeedbackForm />
) : (
<>
{showLoading && (<LoadingSkeleton />)}
{showResults && <Preview results={results} />}
{noResults && <NoResults />}
{hasExceptions && <NoResults message={exceptionOnScrapperResult} />}
</>
)}
</div>
</>
);
Expand Down
2 changes: 1 addition & 1 deletion src/background.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/* eslint-disable @typescript-eslint/no-non-null-assertion */
import {ERROR_MESSAGES, ErrorCodes} from "./error-codes";
import { ERROR_MESSAGES, ErrorCodes } from './error-codes';
import { getCurrentTab, runScrapper } from './utils/chrome';
import { getScrapperOptionsByUrl } from './utils/scrapperUtils';

Expand Down
10 changes: 10 additions & 0 deletions src/components/button.css
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,16 @@
padding: 0.5rem 2rem;
}

.text {
width: auto;
padding: 0;
color: var(--grey);
}

.text:hover {
background: transparent;
}

.primary:hover {
background: var(--button-primary-hover);
}
Expand Down
12 changes: 7 additions & 5 deletions src/components/button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,22 +2,24 @@ import './button.css';
import { FunctionComponent, ComponentChildren } from 'preact';

interface Props {
onClick: () => void;
onClick?: () => void;
children: ComponentChildren;
type?: 'text' | 'primary' | 'secondary';
variant?: 'text' | 'primary' | 'secondary' | 'from';
type?: string;
className?: string;
size?: 'small';
}

const Button: FunctionComponent<Props> = ({
onClick,
className,
type = 'text',
className = '',
variant = 'text',
type,
size = '',
children,
}) => {
return (
<button className={`btn ${className} ${type} ${size}`} onClick={onClick}>
<button type={type} className={`btn ${className} ${variant} ${size}`} onClick={onClick}>
{children}
</button>
);
Expand Down
88 changes: 88 additions & 0 deletions src/components/feedback-form.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import { JSX } from 'preact';
import { useState, useRef, useEffect } from 'preact/hooks';
import {createNewReportEntryRow} from '../utils/rows-api/report';
import Button from './button';

const FeedbackForm = (): JSX.Element => {
const [reason, setReason] = useState('table not detected');
const [feedback, setFeedback] = useState('');
const inputElement = useRef<HTMLInputElement>(null);
const shouldShowInput = reason === 'other';

useEffect(() => {
if (inputElement.current && shouldShowInput) {
inputElement.current.focus();
}
}, [reason]);

const handleRadioInputChange: JSX.GenericEventHandler<HTMLInputElement> = (event) => {
setReason(event.currentTarget.id);
setFeedback('');
}

const handleInputTextChange: JSX.GenericEventHandler<HTMLInputElement> = (event) => {
setFeedback(event.currentTarget.value);
}

const handleSubmit: JSX.SubmitEventHandler<HTMLFormElement> = (event) => {
event.preventDefault();
setTimeout(() => window.close(), 150);
createNewReportEntryRow(feedback ? feedback : reason);
}

return (
<form className="form" onSubmit={handleSubmit}>
<div className="input-wrapper">
<input
type="radio"
id="table not detected"
name="reason"
value={reason}
onChange={handleRadioInputChange}
/>
<label htmlFor="table not detected">Table wasn't detected</label>
</div>

<div className="input-wrapper">
<input
type="radio"
id="table broken"
name="reason"
value={reason}
onChange={handleRadioInputChange}
/>
<label htmlFor="table broken">Table is broken</label>
</div>

<div className="input-wrapper">
<input
type="radio"
id="other"
name="reason"
value={reason}
onChange={handleRadioInputChange}
/>
<label htmlFor="other">Other</label>
</div>


<div className="input-wrapper">
<input
ref={inputElement}
type="text"
maxLength={30}
value={feedback}
placeholder="Write your feedback"
onChange={handleInputTextChange}
disabled={!shouldShowInput}
/>
</div>

<Button type="submit" variant="primary">
Report
</Button>
</form>
);
};

export default FeedbackForm;
7 changes: 7 additions & 0 deletions src/components/header.css
Original file line number Diff line number Diff line change
@@ -1,3 +1,10 @@
.header {
display: flex;
justify-content: space-between;
}

.options {
display: flex;
align-items: center;
gap: 1rem;
}
19 changes: 14 additions & 5 deletions src/components/header.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,23 @@
import { JSX } from 'preact';
import { Dispatch } from 'preact/compat';

import './header.css';
import Button from './button';

const Header = (): JSX.Element => {
interface Props {
onReportClick: Dispatch<unknown>;
}

const Header = ({ onReportClick }: Props): JSX.Element => {
return (
<header className="header">
<img src="/logo.svg" />
<Button size="small" onClick={() => window.close()}>
<img src="/icons/close.svg" />
</Button>
<img src="/logo.svg"/>
<div class="options">
<Button variant="text" size="small" onClick={onReportClick as () => void}>Report</Button>
<Button size="small" onClick={() => window.close()}>
<img src="/icons/close.svg"/>
</Button>
</div>
</header>
);
};
Expand Down
Loading