Closes #2413 - Building a chrome extension for twenty to store person/company data into a workspace. (#3430)
* build: create a new vite project for chrome extension * feat: configure theme per the frontend codebase for chrome extension * feat: inject the add to twenty button into linkedin profile page * feat: create the api key form ui and render it on the options page * feat: inject the add to twenty button into linkedin company page * feat: scrape required data from both the user profile and the company profile * refactor: move modules into options because it is the only page using react for now * fix: show add to twenty button without having to reload the single page application * fix: extract domain of the business website instead of scrapping the industry type * feat: store api key to local storage and open options page when trying to store data without setting a key * feat: send data to the backend upon click and store it to the database * fix: open options page upon clicking the extension icon * fix: update terminology from user to person to match the codebase convention * fix: adopt chrome extension to monorepo approach using nx and get the development server working * fix: update vite config for build command to work per the requirement * feat: add instructions in the readme file to install the extension for local testing * fix: move server base url to a dotenv file and replace the hard-coded url * feat: permit user to configure a custom route for the server from the options page * fix: fetch api key and route from local storage and display on options page to inform users of their choices * fix: move front base url to dotenv and replace the hard-coded url * fix: remove the trailing slash from person and company linkedin username * fix: improve code commenting to explain implementation somewhat better * ci: introduce a workflow to build chrome extension to ensure it can be published * fix: format files to display code in a consistent manner per the prettier configuration in codebase * fix: improve the commenting significantly to explain important and hard-to-understand parts of the code * fix: remove unused permissions from the manifest file for publishing to the chrome web store * Add nx * Fix vale --------- Co-authored-by: Charles Bochet <charles@twenty.com>
This commit is contained in:
@ -0,0 +1,146 @@
|
||||
import styled from '@emotion/styled';
|
||||
import { H2Title } from '../../ui/display/typography/components/H2Title';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { TextInput } from '../../ui/input/components/TextInput';
|
||||
import { Button } from '../../ui/input/button/Button';
|
||||
import { Toggle } from '../../ui/input/components/Toggle';
|
||||
|
||||
const StyledContainer = styled.div<{ isToggleOn: boolean }>`
|
||||
width: 400px;
|
||||
margin: 0 auto;
|
||||
background-color: ${({ theme }) => theme.background.primary};
|
||||
padding: ${({ theme }) => theme.spacing(10)};
|
||||
overflow: hidden;
|
||||
transition: height 0.3s ease;
|
||||
|
||||
height: ${({ isToggleOn }) => (isToggleOn ? '450px' : '390px')};
|
||||
max-height: ${({ isToggleOn }) => (isToggleOn ? '450px' : '390px')};
|
||||
`;
|
||||
|
||||
const StyledHeader = styled.header`
|
||||
text-align: center;
|
||||
margin-bottom: ${({ theme }) => theme.spacing(8)};
|
||||
`;
|
||||
|
||||
const StyledImg = styled.img``;
|
||||
|
||||
const StyledMain = styled.main`
|
||||
margin-bottom: ${({ theme }) => theme.spacing(8)};
|
||||
`;
|
||||
|
||||
const StyledFooter = styled.footer`
|
||||
display: flex;
|
||||
`;
|
||||
|
||||
const StyledTitleContainer = styled.div`
|
||||
flex: 0 0 80%;
|
||||
`;
|
||||
|
||||
const StyledToggleContainer = styled.div`
|
||||
flex: 0 0 20%;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
`;
|
||||
|
||||
const StyledSection = styled.div<{ showSection: boolean }>`
|
||||
transition:
|
||||
max-height 0.3s ease,
|
||||
opacity 0.3s ease;
|
||||
overflow: hidden;
|
||||
max-height: ${({ showSection }) => (showSection ? '200px' : '0')};
|
||||
`;
|
||||
|
||||
export const ApiKeyForm = () => {
|
||||
const [apiKey, setApiKey] = useState('');
|
||||
const [route, setRoute] = useState('');
|
||||
const [showSection, setShowSection] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
const getState = async () => {
|
||||
const localStorage = await chrome.storage.local.get();
|
||||
|
||||
if (localStorage.apiKey) {
|
||||
setApiKey(localStorage.apiKey);
|
||||
}
|
||||
|
||||
if (localStorage.serverBaseUrl) {
|
||||
setRoute(localStorage.serverBaseUrl);
|
||||
}
|
||||
};
|
||||
|
||||
void getState();
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
chrome.storage.local.set({ apiKey });
|
||||
}, [apiKey]);
|
||||
|
||||
useEffect(() => {
|
||||
chrome.storage.local.set({ serverBaseUrl: route });
|
||||
}, [route]);
|
||||
|
||||
const handleGenerateClick = () => {
|
||||
window.open(
|
||||
`${import.meta.env.VITE_FRONT_BASE_URL}/settings/developers/api-keys`,
|
||||
);
|
||||
};
|
||||
|
||||
const handleToggle = () => {
|
||||
setShowSection(!showSection);
|
||||
};
|
||||
|
||||
return (
|
||||
<StyledContainer isToggleOn={showSection}>
|
||||
<StyledHeader>
|
||||
<StyledImg src="/logo/32-32.png" alt="Twenty Logo" />
|
||||
</StyledHeader>
|
||||
|
||||
<StyledMain>
|
||||
<H2Title
|
||||
title="Connect your account"
|
||||
description="Input your key to link the extension to your workspace."
|
||||
/>
|
||||
<TextInput
|
||||
label="Api key"
|
||||
value={apiKey}
|
||||
onChange={setApiKey}
|
||||
placeholder="My API key"
|
||||
/>
|
||||
<Button
|
||||
title="Generate a key"
|
||||
fullWidth={false}
|
||||
variant="primary"
|
||||
accent="default"
|
||||
size="small"
|
||||
position="standalone"
|
||||
soon={false}
|
||||
disabled={false}
|
||||
onClick={handleGenerateClick}
|
||||
/>
|
||||
</StyledMain>
|
||||
|
||||
<StyledFooter>
|
||||
<StyledTitleContainer>
|
||||
<H2Title
|
||||
title="Custom route"
|
||||
description="For developers interested in self-hosting or local testing of the extension."
|
||||
/>
|
||||
</StyledTitleContainer>
|
||||
<StyledToggleContainer>
|
||||
<Toggle value={showSection} onChange={handleToggle} />
|
||||
</StyledToggleContainer>
|
||||
</StyledFooter>
|
||||
|
||||
<StyledSection showSection={showSection}>
|
||||
{showSection && (
|
||||
<TextInput
|
||||
label="Route"
|
||||
value={route}
|
||||
onChange={setRoute}
|
||||
placeholder="My Route"
|
||||
/>
|
||||
)}
|
||||
</StyledSection>
|
||||
</StyledContainer>
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user