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:
Abdullah
2024-02-12 16:30:23 +05:00
committed by GitHub
parent a15128df36
commit 1265dc74d0
59 changed files with 2103 additions and 10 deletions

View File

@ -0,0 +1,106 @@
import handleQueryParams from '../utils/handleQueryParams';
import requestDb from '../utils/requestDb';
import createNewButton from './createButton';
import extractCompanyLinkedinLink from './utils/extractCompanyLinkedinLink';
import extractDomain from './utils/extractDomain';
function insertButtonForCompany(): void {
// Select the element in which to create the button.
const parentDiv: HTMLDivElement | null = document.querySelector(
'.org-top-card-primary-actions__inner',
);
// Create the button with desired callback funciton to execute upon click.
if (parentDiv) {
const newButtonCompany: HTMLButtonElement = createNewButton(
'Add to Twenty',
async () => {
// Extract company-specific data from the DOM
const companyNameElement = document.querySelector(
'.org-top-card-summary__title',
);
const domainNameElement = document.querySelector(
'.org-top-card-primary-actions__inner a',
);
const addressElement = document.querySelectorAll(
'.org-top-card-summary-info-list__info-item',
)[1];
const employeesNumberElement = document.querySelectorAll(
'.org-top-card-summary-info-list__info-item',
)[3];
// Get the text content or other necessary data from the DOM elements
const companyName = companyNameElement
? companyNameElement.getAttribute('title')
: '';
const domainName = extractDomain(
domainNameElement && domainNameElement.getAttribute('href'),
);
const address = addressElement
? addressElement.textContent?.trim().replace(/\s+/g, ' ')
: '';
const employees = employeesNumberElement
? Number(
employeesNumberElement.textContent
?.trim()
.replace(/\s+/g, ' ')
.split('-')[0],
)
: 0;
// Prepare company data to send to the backend
const companyData = {
name: companyName,
domainName: domainName,
address: address,
employees: employees,
linkedinLink: { url: '', label: '' },
};
// Extract active tab url using chrome API - an event is triggered here and is caught by background script.
const { url: activeTabUrl } = await chrome.runtime.sendMessage({
action: 'getActiveTabUrl',
});
// Convert URLs like https://www.linkedin.com/company/twenty/about/ to https://www.linkedin.com/company/twenty
const companyURL = extractCompanyLinkedinLink(activeTabUrl);
companyData.linkedinLink = { url: companyURL, label: companyURL };
const query = `mutation CreateOneCompany { createCompany(data:{${handleQueryParams(
companyData,
)}}) {id} }`;
const response = await requestDb(query);
if (response.data) {
newButtonCompany.textContent = 'Saved';
newButtonCompany.setAttribute('disabled', 'true');
// Button specific styles once the button is unclickable after successfully sending data to server.
newButtonCompany.addEventListener('mouseenter', () => {
const hoverStyles = {
backgroundColor: 'black',
borderColor: 'black',
cursor: 'default',
};
Object.assign(newButtonCompany.style, hoverStyles);
});
} else {
newButtonCompany.textContent = 'Try Again';
}
},
);
// Include the button in the DOM.
parentDiv.prepend(newButtonCompany);
// Write button specific styles here - common ones can be found in createButton.ts.
const buttonSpecificStyles = {
alignSelf: 'end',
};
Object.assign(newButtonCompany.style, buttonSpecificStyles);
}
}
export default insertButtonForCompany;