feat: atualiza estrutura de estilos e build process

- Migra CSS para SCSS com variáveis Bootstrap
- Adiciona script de build para compilação SCSS
- Implementa tipos TypeScript para mensagens
- Atualiza componente popup para React
- Refatora background script com tipagem forte

Refs #ISSUE-123
This commit is contained in:
Lucas Santana 2024-12-19 08:33:15 -03:00
parent 61978a72a1
commit 0cac5e9bb0
7 changed files with 236 additions and 40 deletions

36
scripts/build-styles.js Normal file
View File

@ -0,0 +1,36 @@
import sass from 'sass';
import { writeFileSync, mkdirSync } from 'fs';
import { resolve, dirname } from 'path';
import { fileURLToPath } from 'url';
const __dirname = dirname(fileURLToPath(import.meta.url));
// Compilar SCSS para CSS
function compileSass(inputFile, outputFile) {
try {
const result = sass.compile(inputFile, {
style: 'compressed',
loadPaths: [resolve(__dirname, '../src/styles')]
});
// Criar diretório de saída se não existir
mkdirSync(dirname(outputFile), { recursive: true });
// Escrever arquivo CSS
writeFileSync(outputFile, result.css);
console.log(`SCSS compilado: ${inputFile} -> ${outputFile}`);
} catch (error) {
console.error(`Erro ao compilar SCSS ${inputFile}:`, error);
}
}
// Compilar arquivos
compileSass(
resolve(__dirname, '../src/styles/content.scss'),
resolve(__dirname, '../dist/styles/content.css')
);
compileSass(
resolve(__dirname, '../src/styles/custom.scss'),
resolve(__dirname, '../dist/styles/custom.css')
);

View File

@ -1,8 +1,46 @@
let currentPostData = {};
let authToken: string | null = null;
let negocios_id: string | null = null;
let negocios_nome: string | null = null;
let user_id: string | null = null;
import { MessagePayload, MessageTypes, AuthState, SavePostData } from '../types/messages';
const initialAuthState: AuthState = {
isAuthenticated: false,
token: null,
businessId: null,
businessName: null,
userId: null
};
let authState: AuthState = { ...initialAuthState };
// Handler de mensagens tipado
function handleMessage(
message: MessagePayload,
sender: chrome.runtime.MessageSender,
sendResponse: (response: { success: boolean; error?: string; data?: unknown }) => void
): boolean {
switch (message.type) {
case MessageTypes.SAVE_POST:
handleSavePost(message.data as SavePostData, sendResponse);
break;
case MessageTypes.CHECK_AUTH:
sendResponse({ success: true, data: authState.isAuthenticated });
break;
case MessageTypes.LOGIN:
handleLogin(message.data as { email: string; password: string }, sendResponse);
break;
case MessageTypes.LOGOUT:
handleLogout(sendResponse);
break;
default:
sendResponse({ success: false, error: 'Tipo de mensagem não suportado' });
}
return true; // Indica que a resposta será assíncrona
}
chrome.runtime.onMessage.addListener(handleMessage);
// URL base da API do Bubble no Launchr
const API_URL = 'https://launchr.com.br/api/1.1/wf';
@ -14,8 +52,8 @@ let loginWindowId: number | null = null;
const checkAuth = (): Promise<boolean> => {
return new Promise((resolve) => {
chrome.storage.local.get(['authToken'], (result) => {
authToken = result.authToken;
resolve(!!authToken);
authState.token = result.authToken;
resolve(!!authState.token);
});
});
};
@ -48,29 +86,6 @@ chrome.windows.onRemoved.addListener((windowId) => {
}
});
// Listener para mensagens do content script e popup
chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
if (request.type === 'SAVE_POST' || request.type === 'SAVE_IDEA') {
checkAuth().then(isAuthenticated => {
if (!isAuthenticated) {
openLoginPopup();
sendResponse({ success: false, error: 'AUTH_REQUIRED' });
} else {
// Implementar lógica de salvamento
handleSaveRequest(request, sendResponse);
}
});
return true; // Indica que a resposta será assíncrona
}
if (request.type === 'CHECK_AUTH') {
checkAuth().then(isAuthenticated => {
sendResponse({ isAuthenticated });
});
return true;
}
});
// Listener para o clique no ícone da extensão
chrome.action.onClicked.addListener(async () => {
const isAuthenticated = await checkAuth();
@ -97,7 +112,7 @@ const handleSaveRequest = async (request: any, sendResponse: (response: any) =>
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': authToken || ''
'Authorization': authState.token || ''
},
body: JSON.stringify(request.data)
});
@ -106,7 +121,7 @@ const handleSaveRequest = async (request: any, sendResponse: (response: any) =>
if (response.status === 401) {
// Token inválido ou expirado
chrome.storage.local.remove(['authToken']);
authToken = null;
authState.token = null;
openLoginPopup();
sendResponse({ success: false, error: 'AUTH_REQUIRED' });
return;

View File

@ -1,14 +1,16 @@
import React from 'react';
import ReactDOM from 'react-dom/client';
import { Provider } from 'react-redux';
import { store } from '../store';
import Router from './Router';
import '../index.css';
import { PopupApp } from './components/PopupApp';
ReactDOM.createRoot(document.getElementById('root')!).render(
<React.StrictMode>
import '../styles/custom.scss';
function Popup() {
return (
<Provider store={store}>
<Router />
<PopupApp />
</Provider>
</React.StrictMode>
);
);
}
export default Popup;

56
src/styles/content.scss Normal file
View File

@ -0,0 +1,56 @@
// Variáveis do tema
@import "variables";
// Estilos do botão
.launchr {
&-save-button {
background: none;
border: none;
padding: $spacer * 0.5;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
transition: $transition-base;
margin-right: $spacer;
border-radius: $border-radius;
&:hover {
background-color: rgba($black, 0.05);
}
&.saving {
opacity: 0.7;
cursor: wait;
}
&.saved {
background-color: rgba($primary, 0.1);
}
}
&-icon {
width: 20px;
height: 20px;
border-radius: $border-radius-sm;
}
&-save-button-container {
display: inline-flex;
align-items: center;
margin-left: $spacer;
}
}
// Ajustes para o LinkedIn
.feed-shared-control-menu,
.feed-shared-update-v2__control-menu {
.launchr-save-button {
position: relative;
z-index: 2;
}
.display-flex {
align-items: center;
}
}

36
src/styles/custom.scss Normal file
View File

@ -0,0 +1,36 @@
@import "variables";
@import "bootstrap/scss/bootstrap";
// Estilos personalizados da extensão
.launchr {
&-container {
width: 400px;
min-height: 500px;
max-height: 600px;
overflow-y: auto;
}
&-header {
padding: $spacer;
border-bottom: 1px solid $border-color;
background-color: $white;
}
&-content {
padding: $spacer;
}
&-save-button {
@extend .btn;
@extend .btn-primary;
&--saving {
opacity: 0.7;
cursor: wait;
}
&--saved {
@extend .btn-success;
}
}
}

21
src/styles/variables.scss Normal file
View File

@ -0,0 +1,21 @@
// Sobrescrita de variáveis Bootstrap
$primary: #4F46E5;
$primary-hover: #4338CA;
// Cores personalizadas
$launchr-colors: (
"primary": $primary,
"primary-hover": $primary-hover,
"error": #EF4444,
"success": #10B981
);
// Configurações do tema
$enable-shadows: true;
$enable-gradients: false;
$enable-responsive-font-sizes: true;
// Configurações de componentes
$border-radius: 0.375rem;
$btn-border-radius: $border-radius;
$input-border-radius: $border-radius;

30
src/types/messages.d.ts vendored Normal file
View File

@ -0,0 +1,30 @@
export interface MessagePayload {
type: MessageTypes;
data: unknown;
}
export const MessageTypes = {
SAVE_POST: 'SAVE_POST',
SAVE_IDEA: 'SAVE_IDEA',
CHECK_AUTH: 'CHECK_AUTH',
LOGIN: 'LOGIN',
LOGOUT: 'LOGOUT'
} as const;
export type MessageTypes = typeof MessageTypes[keyof typeof MessageTypes];
export interface AuthState {
isAuthenticated: boolean;
token: string | null;
businessId: string | null;
businessName: string | null;
userId: string | null;
}
export interface SavePostData {
title: string;
content: string;
author: string;
authorProfileLink?: string;
postLink?: string;
}