diff --git a/scripts/build-styles.js b/scripts/build-styles.js new file mode 100644 index 0000000..8a02d4a --- /dev/null +++ b/scripts/build-styles.js @@ -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') +); \ No newline at end of file diff --git a/src/background/index.ts b/src/background/index.ts index e186e46..ae3f331 100644 --- a/src/background/index.ts +++ b/src/background/index.ts @@ -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 => { 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; diff --git a/src/popup/index.tsx b/src/popup/index.tsx index f401fad..4feb584 100644 --- a/src/popup/index.tsx +++ b/src/popup/index.tsx @@ -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( - +import '../styles/custom.scss'; + +function Popup() { + return ( - + - -); \ No newline at end of file + ); +} + +export default Popup; \ No newline at end of file diff --git a/src/styles/content.scss b/src/styles/content.scss new file mode 100644 index 0000000..d26a3f9 --- /dev/null +++ b/src/styles/content.scss @@ -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; + } +} \ No newline at end of file diff --git a/src/styles/custom.scss b/src/styles/custom.scss new file mode 100644 index 0000000..6ed831d --- /dev/null +++ b/src/styles/custom.scss @@ -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; + } + } +} \ No newline at end of file diff --git a/src/styles/variables.scss b/src/styles/variables.scss new file mode 100644 index 0000000..bd7ede3 --- /dev/null +++ b/src/styles/variables.scss @@ -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; \ No newline at end of file diff --git a/src/types/messages.d.ts b/src/types/messages.d.ts new file mode 100644 index 0000000..0f63536 --- /dev/null +++ b/src/types/messages.d.ts @@ -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; +} \ No newline at end of file