Corrigindo Popup e Menu
This commit is contained in:
parent
439e26b0a5
commit
4d2609c646
66
.dockerignore
Normal file
66
.dockerignore
Normal file
@ -0,0 +1,66 @@
|
||||
# Controle de versão
|
||||
.git
|
||||
.gitignore
|
||||
.gitattributes
|
||||
.github/
|
||||
|
||||
# Dependências
|
||||
node_modules/
|
||||
package-lock.json
|
||||
yarn.lock
|
||||
|
||||
# Arquivos de build
|
||||
dist/
|
||||
build/
|
||||
*.zip
|
||||
*.crx
|
||||
*.pem
|
||||
|
||||
# Arquivos de ambiente e configuração
|
||||
.env*
|
||||
.chrome/
|
||||
.vscode/
|
||||
.idea/
|
||||
*.config.js
|
||||
*.config.ts
|
||||
|
||||
# Arquivos de documentação
|
||||
README.md
|
||||
CHANGELOG.md
|
||||
LICENSE
|
||||
docs/
|
||||
|
||||
# Arquivos de teste
|
||||
__tests__/
|
||||
test/
|
||||
tests/
|
||||
coverage/
|
||||
.nyc_output/
|
||||
|
||||
# Arquivos do sistema
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
desktop.ini
|
||||
|
||||
# Logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
|
||||
# Arquivos temporários
|
||||
.tmp/
|
||||
.temp/
|
||||
.cache/
|
||||
*.swp
|
||||
*.swo
|
||||
|
||||
# Arquivos específicos da extensão que devem ser baixados/construídos
|
||||
lib/bootstrap.bundle.min.js
|
||||
lib/tinymce/
|
||||
|
||||
# Outros
|
||||
.history/
|
||||
*.bak
|
||||
*.backup
|
||||
*.old
|
||||
55
.gitignore
vendored
Normal file
55
.gitignore
vendored
Normal file
@ -0,0 +1,55 @@
|
||||
# Dependências
|
||||
node_modules/
|
||||
package-lock.json
|
||||
yarn.lock
|
||||
|
||||
# Arquivos de build
|
||||
dist/
|
||||
build/
|
||||
*.zip
|
||||
|
||||
# Arquivos de ambiente
|
||||
.env
|
||||
.env.local
|
||||
.env.*.local
|
||||
|
||||
# Arquivos do sistema
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
desktop.ini
|
||||
|
||||
# Arquivos de IDE/Editor
|
||||
.idea/
|
||||
.vscode/
|
||||
*.swp
|
||||
*.swo
|
||||
*.sublime-workspace
|
||||
*.sublime-project
|
||||
|
||||
# Arquivos de log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
debug.log
|
||||
|
||||
# Arquivos temporários
|
||||
*.tmp
|
||||
*.temp
|
||||
.cache/
|
||||
|
||||
# Arquivos de teste
|
||||
coverage/
|
||||
.nyc_output/
|
||||
|
||||
# Arquivos específicos da extensão
|
||||
lib/bootstrap.bundle.min.js
|
||||
lib/tinymce/
|
||||
|
||||
# Arquivos de configuração local
|
||||
.chrome/
|
||||
|
||||
# Outros
|
||||
*.pem
|
||||
*.crx
|
||||
.history/
|
||||
.env.*
|
||||
30
README.md
30
README.md
@ -0,0 +1,30 @@
|
||||
# Launchr Chrome Extension
|
||||
|
||||
Extensão do Chrome para salvar posts do LinkedIn diretamente no Launchr. Permite capturar conteúdo e gerenciar posts de forma eficiente.
|
||||
|
||||
## Funcionalidades
|
||||
|
||||
- 🔒 Login seguro com sua conta Launchr
|
||||
- 💾 Salvar posts do LinkedIn com um clique
|
||||
- 📝 Criar novos posts diretamente da extensão
|
||||
- 🏢 Gerenciar múltiplos negócios
|
||||
- 📋 Captura automática de:
|
||||
- Autor do post
|
||||
- Link do perfil
|
||||
- Conteúdo do post
|
||||
- URL do post
|
||||
|
||||
## Instalação
|
||||
|
||||
### Para Usuários
|
||||
|
||||
1. Baixe a última versão da extensão
|
||||
2. Abra o Chrome e vá para `chrome://extensions/`
|
||||
3. Ative o "Modo do desenvolvedor" no canto superior direito
|
||||
4. Arraste o arquivo .zip baixado para a página ou clique em "Carregar sem compactação" e selecione a pasta da extensão
|
||||
|
||||
### Para Desenvolvedores
|
||||
|
||||
1. Clone o repositório:
|
||||
```bash
|
||||
git clone https://github.com/seu
|
||||
@ -177,5 +177,56 @@ chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
|
||||
sendResponse({ success: true });
|
||||
});
|
||||
return true;
|
||||
} else if (request.action === 'getUserData') {
|
||||
fetch(`${API_URL}/user/data`, {
|
||||
headers: {
|
||||
'Authorization': authToken
|
||||
}
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
sendResponse({ success: true, data });
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Erro ao buscar dados do usuário:', error);
|
||||
sendResponse({ success: false, error: 'Erro ao buscar dados do usuário' });
|
||||
});
|
||||
|
||||
return true;
|
||||
} else if (request.action === 'saveSwipeFile') {
|
||||
fetch(`${API_URL}/novo_swipe_file`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': authToken
|
||||
},
|
||||
body: JSON.stringify(request.data)
|
||||
})
|
||||
.then(response => {
|
||||
if (!response.ok) throw new Error('Erro na requisição');
|
||||
return response.json();
|
||||
})
|
||||
.then(data => {
|
||||
sendResponse({ success: true, data });
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Erro ao salvar swipe file:', error);
|
||||
sendResponse({ success: false, error: error.message });
|
||||
});
|
||||
|
||||
return true;
|
||||
} else if (request.action === 'openPopupWithData') {
|
||||
// Armazenar os dados temporariamente
|
||||
chrome.storage.local.set({ currentPostData: request.data }, () => {
|
||||
// Criar uma nova janela popup
|
||||
chrome.windows.create({
|
||||
url: 'popup.html',
|
||||
type: 'popup',
|
||||
width: 400,
|
||||
height: 600,
|
||||
focused: true
|
||||
});
|
||||
});
|
||||
return true;
|
||||
}
|
||||
});
|
||||
75
content.css
75
content.css
@ -1,60 +1,45 @@
|
||||
/* Estilos do botão */
|
||||
.launchr-save-button {
|
||||
display: inline-flex;
|
||||
background: none;
|
||||
border: none;
|
||||
padding: 4px;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 8px 16px;
|
||||
transition: all 0.2s;
|
||||
margin-right: 8px;
|
||||
border-radius: 4px;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
background-color: #4F46E5;
|
||||
color: white;
|
||||
border: none;
|
||||
margin-left: 8px;
|
||||
min-width: 150px;
|
||||
}
|
||||
|
||||
.launchr-save-button:hover:not(:disabled) {
|
||||
background-color: #4338CA;
|
||||
.launchr-save-button:hover {
|
||||
background-color: rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
.launchr-save-button:disabled {
|
||||
.launchr-icon {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
/* Posicionamento no menu de controle */
|
||||
.feed-shared-control-menu .launchr-save-button,
|
||||
.feed-shared-update-v2__control-menu .launchr-save-button {
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.launchr-save-button.saving {
|
||||
opacity: 0.7;
|
||||
cursor: not-allowed;
|
||||
cursor: wait;
|
||||
}
|
||||
|
||||
.launchr-save-button.loading {
|
||||
background-color: #6B7280;
|
||||
.launchr-save-button.saved {
|
||||
background-color: rgba(10, 102, 194, 0.1);
|
||||
}
|
||||
|
||||
.launchr-save-button.success {
|
||||
background-color: #10B981;
|
||||
}
|
||||
|
||||
.launchr-save-button.error {
|
||||
background-color: #EF4444;
|
||||
}
|
||||
|
||||
.launchr-save-button.auth {
|
||||
background-color: #F59E0B;
|
||||
}
|
||||
|
||||
/* Animação de loading */
|
||||
.launchr-save-button.loading::after {
|
||||
content: '';
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
margin-left: 8px;
|
||||
border: 2px solid #fff;
|
||||
border-radius: 50%;
|
||||
border-top-color: transparent;
|
||||
animation: spin 1s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
/* Ajustes para o container do botão */
|
||||
.feed-shared-control-menu .display-flex,
|
||||
.feed-shared-update-v2__control-menu .display-flex {
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
167
content.js
167
content.js
@ -1,74 +1,109 @@
|
||||
// Função para extrair dados do post atual
|
||||
function extractPostData() {
|
||||
// Selecionar o post atual
|
||||
const post = document.querySelector('.occludable-update');
|
||||
|
||||
if (!post) {
|
||||
console.error('Post não encontrado.');
|
||||
return null;
|
||||
}
|
||||
|
||||
// Extrair informações do post
|
||||
const contentElement = post.querySelector('.feed-shared-update-v2__description, .break-words, [data-test-id="feed-update-message"]');
|
||||
const authorElement = post.querySelector('.feed-shared-actor__name, .update-components-actor__name');
|
||||
const dateElement = post.querySelector('span.feed-shared-actor__sub-description span[aria-hidden="true"], span.update-components-actor__sub-description time');
|
||||
|
||||
const postContent = contentElement ? contentElement.innerText.trim() : '';
|
||||
const postAuthor = authorElement ? authorElement.innerText.trim() : '';
|
||||
|
||||
// Obter o link do perfil do autor
|
||||
const authorProfileLinkElement = authorElement ? authorElement.closest('a') : null;
|
||||
const baseUrl = 'https://www.linkedin.com';
|
||||
const authorProfileLink = authorProfileLinkElement ? new URL(authorProfileLinkElement.getAttribute('href'), baseUrl).href : '';
|
||||
|
||||
// Obter a data do post
|
||||
const postDate = dateElement ? dateElement.innerText.trim() : '';
|
||||
|
||||
// Obter o link do post
|
||||
const postLinkElement = post.querySelector('a[href*="/feed/update/"], a[data-control-name="share_link"], a[href*="/posts/"]');
|
||||
const postLink = postLinkElement ? postLinkElement.href : window.location.href;
|
||||
|
||||
const postData = {
|
||||
content: postContent,
|
||||
author: postAuthor,
|
||||
authorProfileLink: authorProfileLink,
|
||||
date: postDate,
|
||||
postLink: postLink
|
||||
};
|
||||
|
||||
return postData;
|
||||
function createSaveButton() {
|
||||
const button = document.createElement('button');
|
||||
button.className = 'launchr-save-button';
|
||||
button.title = 'Salvar no Launchr'; // Tooltip ao passar o mouse
|
||||
|
||||
// Adicionar ícone do Launchr
|
||||
const icon = document.createElement('img');
|
||||
icon.src = chrome.runtime.getURL('images/logo.png');
|
||||
icon.alt = 'Launchr';
|
||||
icon.className = 'launchr-icon';
|
||||
|
||||
button.appendChild(icon);
|
||||
|
||||
return button;
|
||||
}
|
||||
|
||||
// Adicionar um botão na página para salvar o post
|
||||
function addSavePostButton() {
|
||||
const button = document.createElement('button');
|
||||
button.innerText = 'Salvar Post';
|
||||
button.id = 'savePostButton';
|
||||
button.style.position = 'fixed';
|
||||
button.style.top = '10px';
|
||||
button.style.right = '10px';
|
||||
button.style.zIndex = '9999';
|
||||
function extractPostData(postElement) {
|
||||
// Encontrar o autor
|
||||
const authorElement = postElement.querySelector('.feed-shared-actor__name, .update-components-actor__name');
|
||||
const author = authorElement?.textContent?.trim() || '';
|
||||
|
||||
button.addEventListener('click', () => {
|
||||
const postData = extractPostData();
|
||||
// Encontrar o link do perfil do autor
|
||||
const authorProfileLink = authorElement?.closest('a')?.href || '';
|
||||
|
||||
if (postData) {
|
||||
// Enviar os dados do post para o background script
|
||||
chrome.runtime.sendMessage({ action: 'storePostData', data: postData }, (response) => {
|
||||
if (response && response.success) {
|
||||
console.log('Dados do post enviados para o background script.');
|
||||
alert('Dados do post prontos para serem salvos. Clique no ícone da extensão para prosseguir.');
|
||||
} else {
|
||||
console.error('Erro ao enviar dados do post:', response.error);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
alert('Não foi possível extrair os dados do post.');
|
||||
// Encontrar o link do post
|
||||
const postLinkElement = postElement.querySelector('a[href*="/feed/update/"], a[data-control-name="share_link"]');
|
||||
const postLink = postLinkElement?.href || window.location.href;
|
||||
|
||||
// Encontrar o conteúdo do post
|
||||
const contentElement = postElement.querySelector('.feed-shared-update-v2__description-wrapper, .feed-shared-update-v2__description, .feed-shared-text');
|
||||
const content = contentElement?.textContent?.trim() || '';
|
||||
|
||||
return {
|
||||
author,
|
||||
authorProfileLink,
|
||||
postLink,
|
||||
content
|
||||
};
|
||||
}
|
||||
|
||||
function addSaveButtonToPost(postElement) {
|
||||
// Verificar se o botão já existe
|
||||
if (postElement.querySelector('.launchr-save-button')) {
|
||||
return;
|
||||
}
|
||||
});
|
||||
|
||||
document.body.appendChild(button);
|
||||
// Encontrar o menu de controle do post
|
||||
const controlMenu = postElement.querySelector('.feed-shared-control-menu, .feed-shared-update-v2__control-menu');
|
||||
if (!controlMenu) return;
|
||||
|
||||
// Criar container para o botão (para manter o alinhamento)
|
||||
const buttonContainer = document.createElement('div');
|
||||
buttonContainer.className = 'display-flex align-items-center';
|
||||
|
||||
// Criar e adicionar o botão
|
||||
const saveButton = createSaveButton();
|
||||
buttonContainer.appendChild(saveButton);
|
||||
|
||||
// Inserir antes do menu de controle existente
|
||||
controlMenu.insertBefore(buttonContainer, controlMenu.firstChild);
|
||||
|
||||
// Adicionar evento de clique
|
||||
saveButton.addEventListener('click', async (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
try {
|
||||
saveButton.classList.add('saving');
|
||||
const postData = extractPostData(postElement);
|
||||
|
||||
// Armazenar dados do post temporariamente
|
||||
chrome.storage.local.set({ currentPostData: postData }, () => {
|
||||
// Abrir o popup usando chrome.action
|
||||
chrome.runtime.sendMessage({
|
||||
action: 'openPopupWithData',
|
||||
data: postData
|
||||
});
|
||||
});
|
||||
|
||||
saveButton.classList.remove('saving');
|
||||
} catch (error) {
|
||||
console.error('Erro ao processar post:', error);
|
||||
saveButton.classList.remove('saving');
|
||||
alert('Erro ao processar o post. Por favor, tente novamente.');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Chamar a função para adicionar o botão
|
||||
addSavePostButton();
|
||||
// Observer para detectar novos posts
|
||||
const observer = new MutationObserver((mutations) => {
|
||||
mutations.forEach((mutation) => {
|
||||
mutation.addedNodes.forEach((node) => {
|
||||
if (node instanceof Element) {
|
||||
// Procurar por posts no elemento adicionado
|
||||
const posts = node.querySelectorAll('.feed-shared-update-v2, .occludable-update');
|
||||
posts.forEach(addSaveButtonToPost);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// Iniciar observação
|
||||
observer.observe(document.body, {
|
||||
childList: true,
|
||||
subtree: true
|
||||
});
|
||||
|
||||
// Adicionar botões aos posts existentes
|
||||
document.querySelectorAll('.feed-shared-update-v2, .occludable-update').forEach(addSaveButtonToPost);
|
||||
6
lib/bootstrap.min.css
vendored
Normal file
6
lib/bootstrap.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
@ -1,5 +1,5 @@
|
||||
{
|
||||
"manifest_version": 2,
|
||||
"manifest_version": 3,
|
||||
"name": "Sua Extensão",
|
||||
"version": "1.0",
|
||||
"description": "Extensão para salvar posts do LinkedIn e gerar mensagens personalizadas.",
|
||||
@ -7,15 +7,19 @@
|
||||
"activeTab",
|
||||
"storage",
|
||||
"contextMenus",
|
||||
"tabs",
|
||||
"scripting",
|
||||
"windows"
|
||||
],
|
||||
"host_permissions": [
|
||||
"https://launchr.com.br/*",
|
||||
"https://api.openai.com/*"
|
||||
"https://api.openai.com/*",
|
||||
"*://www.linkedin.com/*"
|
||||
],
|
||||
"background": {
|
||||
"scripts": ["background.js"],
|
||||
"persistent": false
|
||||
"service_worker": "background.js",
|
||||
"type": "module"
|
||||
},
|
||||
"browser_action": {
|
||||
"action": {
|
||||
"default_popup": "popup.html",
|
||||
"default_title": "Minha Extensão",
|
||||
"default_icon": {
|
||||
@ -27,6 +31,7 @@
|
||||
"content_scripts": [
|
||||
{
|
||||
"matches": ["*://www.linkedin.com/*"],
|
||||
"css": ["content.css"],
|
||||
"js": ["content.js"]
|
||||
}
|
||||
],
|
||||
@ -34,5 +39,12 @@
|
||||
"16": "icon16.png",
|
||||
"48": "icon48.png",
|
||||
"128": "icon128.png"
|
||||
}
|
||||
},
|
||||
"web_accessible_resources": [{
|
||||
"resources": [
|
||||
"images/*",
|
||||
"lib/*"
|
||||
],
|
||||
"matches": ["<all_urls>"]
|
||||
}]
|
||||
}
|
||||
160
popup.html
160
popup.html
@ -2,52 +2,144 @@
|
||||
<html lang="pt-BR">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Salvar Post</title>
|
||||
<link rel="stylesheet" href="styles.css">
|
||||
<title>Launchr</title>
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css">
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<h2>Salvar Post</h2>
|
||||
<form id="postForm">
|
||||
<label for="author">Autor:</label>
|
||||
<input type="text" id="author" name="author" readonly>
|
||||
<body class="bg-light" style="width: 400px; min-height: 500px;">
|
||||
<!-- Área de Login -->
|
||||
<div id="loginSection" class="container p-4">
|
||||
<!-- Logo -->
|
||||
<div class="d-flex align-items-center mb-4">
|
||||
<img src="./images/logo.png" alt="Launchr" height="32" class="me-2">
|
||||
<small class="text-muted">Version 1.3.6</small>
|
||||
</div>
|
||||
|
||||
<label for="authorProfileLink">Link do Perfil do Autor:</label>
|
||||
<input type="text" id="authorProfileLink" name="authorProfileLink" readonly>
|
||||
<h1 class="display-6 text-primary mb-4">Let's get started</h1>
|
||||
|
||||
<form id="loginForm">
|
||||
<div class="mb-3">
|
||||
<label for="email" class="form-label">Email</label>
|
||||
<div class="input-group">
|
||||
<span class="input-group-text">
|
||||
<i class="bi bi-envelope"></i>
|
||||
</span>
|
||||
<input type="email" class="form-control" id="email" required>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<label for="postLink">Link do Post:</label>
|
||||
<input type="text" id="postLink" name="postLink" readonly>
|
||||
<div class="mb-3">
|
||||
<label for="password" class="form-label">Password</label>
|
||||
<div class="input-group">
|
||||
<span class="input-group-text">
|
||||
<i class="bi bi-lock"></i>
|
||||
</span>
|
||||
<input type="password" class="form-control" id="password" required>
|
||||
<button class="btn btn-outline-secondary" type="button" id="togglePasswordBtn">
|
||||
<i class="bi bi-eye"></i>
|
||||
</button>
|
||||
</div>
|
||||
<a href="#" class="text-decoration-none small d-block mt-2">Forgot password?</a>
|
||||
</div>
|
||||
|
||||
<label for="date">Data:</label>
|
||||
<input type="text" id="date" name="date" readonly>
|
||||
<button type="submit" class="btn btn-primary w-100 py-2 mt-3">LOGIN</button>
|
||||
|
||||
<label for="content">Conteúdo:</label>
|
||||
<textarea id="content" name="content" rows="4" readonly></textarea>
|
||||
|
||||
<label for="category">Categoria:</label>
|
||||
<select id="category" name="category">
|
||||
<option value="">Selecione uma categoria</option>
|
||||
<option value="Marketing">Marketing</option>
|
||||
<option value="Vendas">Vendas</option>
|
||||
<option value="Tecnologia">Tecnologia</option>
|
||||
<!-- Adicione outras categorias conforme necessário -->
|
||||
</select>
|
||||
|
||||
<button type="button" id="saveButton">Salvar</button>
|
||||
<div class="text-center mt-4">
|
||||
<span class="text-muted">New here? </span>
|
||||
<a href="#" class="text-decoration-none">Create your account</a>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<!-- Popup de confirmação -->
|
||||
<div id="successPopup" class="popup" style="display: none;">
|
||||
<div class="popup-content">
|
||||
<p>O post foi enviado com sucesso!</p>
|
||||
<div class="popup-buttons">
|
||||
<button id="cancelButton">Fechar</button>
|
||||
<button id="viewButton">Visualizar Swipe File</button>
|
||||
<!-- Área Logada -->
|
||||
<div id="loggedInSection" class="d-none">
|
||||
<!-- Navbar -->
|
||||
<nav class="navbar navbar-expand navbar-light bg-white border-bottom px-3">
|
||||
<div class="container-fluid px-0">
|
||||
<!-- Logo -->
|
||||
<a class="navbar-brand" href="#">
|
||||
<img src="./images/logo.png" alt="Launchr" height="32">
|
||||
</a>
|
||||
|
||||
<!-- Links centralizados -->
|
||||
<div class="navbar-nav mx-auto">
|
||||
<button id="navSwipeFile" class="nav-link active fw-medium border-0 bg-transparent px-4">
|
||||
SWIPE FILE
|
||||
</button>
|
||||
<button id="navNewPost" class="nav-link fw-medium border-0 bg-transparent px-4">
|
||||
NOVO POST
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Avatar do usuário com dropdown -->
|
||||
<div class="navbar-nav">
|
||||
<div class="nav-item dropdown">
|
||||
<div class="rounded-circle bg-primary d-flex align-items-center justify-content-center"
|
||||
style="width: 40px; height: 40px; cursor: pointer;"
|
||||
id="userDropdown"
|
||||
data-bs-toggle="dropdown"
|
||||
aria-expanded="false">
|
||||
<span class="text-white fw-medium">IL</span>
|
||||
</div>
|
||||
<ul class="dropdown-menu dropdown-menu-end py-2" style="min-width: 200px;" aria-labelledby="userDropdown">
|
||||
<!-- Seleção de Negócio -->
|
||||
<li class="px-3 mb-2">
|
||||
<small class="text-muted d-block mb-1">Selecione o Negócio</small>
|
||||
<select id="negociosSelect" class="form-select form-select-sm">
|
||||
<!-- Opções serão preenchidas via JavaScript -->
|
||||
</select>
|
||||
</li>
|
||||
<li><hr class="dropdown-divider"></li>
|
||||
<li><button class="dropdown-item text-danger" id="logoutButton">Sair</button></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<!-- Conteúdo Principal -->
|
||||
<div class="container p-4">
|
||||
<!-- Seção Swipe File -->
|
||||
<div id="swipeFileSection">
|
||||
<form id="postForm" class="bg-white p-4 rounded shadow-sm">
|
||||
<div class="mb-3">
|
||||
<label for="author" class="form-label">Autor</label>
|
||||
<input type="text" class="form-control" id="author" readonly>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="content" class="form-label">Conteúdo</label>
|
||||
<textarea class="form-control" id="content" rows="4"></textarea>
|
||||
</div>
|
||||
|
||||
<button type="button" id="saveSwipeFileButton" class="btn btn-primary w-100">
|
||||
Enviar Post
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<!-- Seção Novo Post -->
|
||||
<div id="newPostSection" class="d-none">
|
||||
<form id="newPostForm" class="bg-white p-4 rounded shadow-sm">
|
||||
<div class="mb-3">
|
||||
<label for="postTitle" class="form-label">Título do Post</label>
|
||||
<input type="text" class="form-control" id="postTitle">
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="postEditor" class="form-label">Conteúdo do Post</label>
|
||||
<textarea id="postEditor" class="form-control" rows="6"></textarea>
|
||||
</div>
|
||||
|
||||
<button type="button" id="savePostButton" class="btn btn-primary w-100">
|
||||
Publicar Post
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="lib/bootstrap.bundle.min.js"></script>
|
||||
<script src="popup.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
349
popup.js
349
popup.js
@ -1,130 +1,241 @@
|
||||
let editorLoaded = false;
|
||||
|
||||
async function loadEditor() {
|
||||
if (editorLoaded) return;
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
const script = document.createElement('script');
|
||||
script.src = 'tinymce/tinymce.min.js';
|
||||
script.onload = async () => {
|
||||
const { initTinyMCE } = await import('./src/editor.js');
|
||||
initTinyMCE();
|
||||
editorLoaded = true;
|
||||
resolve();
|
||||
};
|
||||
script.onerror = reject;
|
||||
document.head.appendChild(script);
|
||||
});
|
||||
}
|
||||
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
// Recuperar os dados do post do background script
|
||||
chrome.runtime.sendMessage({ action: 'getPostData' }, (response) => {
|
||||
if (response && response.data) {
|
||||
const postData = response.data;
|
||||
|
||||
// Preencher os campos com os dados do post
|
||||
document.getElementById('author').value = postData.author || '';
|
||||
document.getElementById('authorProfileLink').value = postData.authorProfileLink || '';
|
||||
document.getElementById('postLink').value = postData.postLink || '';
|
||||
document.getElementById('date').value = postData.date || '';
|
||||
document.getElementById('content').value = postData.content || '';
|
||||
} else {
|
||||
console.log('Nenhum dado do post disponível.');
|
||||
}
|
||||
});
|
||||
|
||||
// Referência ao popup e aos botões
|
||||
const successPopup = document.getElementById('successPopup');
|
||||
const cancelButton = document.getElementById('cancelButton');
|
||||
const viewButton = document.getElementById('viewButton');
|
||||
|
||||
let negocios_nome = '';
|
||||
let negocios_id = '';
|
||||
|
||||
// Recuperar negocios_nome e negocios_id do storage
|
||||
chrome.storage.local.get(['negocios_nome', 'negocios_id'], (result) => {
|
||||
if (result.negocios_nome && result.negocios_id) {
|
||||
negocios_nome = result.negocios_nome;
|
||||
negocios_id = result.negocios_id;
|
||||
} else {
|
||||
console.error('Não foi possível obter as informações do negócio.');
|
||||
}
|
||||
});
|
||||
|
||||
// Função para exibir o popup de sucesso
|
||||
function showSuccessPopup() {
|
||||
successPopup.style.display = 'flex';
|
||||
// Inicializar o dropdown do usuário
|
||||
const userDropdown = document.getElementById('userDropdown');
|
||||
if (userDropdown) {
|
||||
new bootstrap.Dropdown(userDropdown);
|
||||
}
|
||||
|
||||
// Evento para o botão de cancelar e fechar o popup
|
||||
cancelButton.addEventListener('click', () => {
|
||||
successPopup.style.display = 'none';
|
||||
// Fechar o popup principal
|
||||
window.close();
|
||||
|
||||
// Inicializar todos os dropdowns do Bootstrap
|
||||
const dropdownElementList = [].slice.call(document.querySelectorAll('[data-bs-toggle="dropdown"]'));
|
||||
const dropdownList = dropdownElementList.map(function (dropdownToggleEl) {
|
||||
return new bootstrap.Dropdown(dropdownToggleEl);
|
||||
});
|
||||
|
||||
// Evento para o botão de visualizar o Swipe File
|
||||
viewButton.addEventListener('click', () => {
|
||||
if (negocios_nome && negocios_id) {
|
||||
// Construir o link para visualizar o Swipe File
|
||||
const baseUrl = 'https://launchr.com.br';
|
||||
const encodedNegociosNome = encodeURIComponent(negocios_nome);
|
||||
const swipeFileUrl = `${baseUrl}/posts/${encodedNegociosNome}-${negocios_id}?tab=Swipe%20File`;
|
||||
|
||||
// Abrir o link em uma nova aba
|
||||
chrome.tabs.create({ url: swipeFileUrl }, () => {
|
||||
// Fechar o popup após abrir a nova aba
|
||||
window.close();
|
||||
|
||||
const loginSection = document.getElementById('loginSection');
|
||||
const loggedInSection = document.getElementById('loggedInSection');
|
||||
|
||||
// Verificar estado de autenticação
|
||||
chrome.storage.local.get(['authToken', 'negocios_id', 'negocios_nome'], (result) => {
|
||||
if (result.authToken) {
|
||||
showLoggedInSection();
|
||||
} else {
|
||||
showLoginSection();
|
||||
}
|
||||
});
|
||||
|
||||
// Funções de navegação
|
||||
function setActiveNav(activeId) {
|
||||
['navSwipeFile', 'navNewPost'].forEach(id => {
|
||||
const element = document.getElementById(id);
|
||||
if (element) {
|
||||
if (id === activeId) {
|
||||
element.classList.add('active');
|
||||
element.style.borderBottom = '2px solid var(--bs-primary)';
|
||||
} else {
|
||||
element.classList.remove('active');
|
||||
element.style.borderBottom = 'none';
|
||||
}
|
||||
}
|
||||
});
|
||||
} else {
|
||||
alert('Informações do negócio não disponíveis.');
|
||||
}
|
||||
|
||||
// Mostrar/esconder seções
|
||||
const swipeFileSection = document.getElementById('swipeFileSection');
|
||||
const newPostSection = document.getElementById('newPostSection');
|
||||
|
||||
if (activeId === 'navSwipeFile') {
|
||||
swipeFileSection.classList.remove('d-none');
|
||||
newPostSection.classList.add('d-none');
|
||||
} else {
|
||||
swipeFileSection.classList.add('d-none');
|
||||
newPostSection.classList.remove('d-none');
|
||||
}
|
||||
}
|
||||
|
||||
// Event listeners do menu
|
||||
document.getElementById('navSwipeFile')?.addEventListener('click', () => {
|
||||
setActiveNav('navSwipeFile');
|
||||
});
|
||||
|
||||
// Lidar com o clique no botão de salvar
|
||||
const saveButton = document.getElementById('saveButton');
|
||||
if (saveButton) {
|
||||
saveButton.addEventListener('click', () => {
|
||||
try {
|
||||
// Obter dados atualizados do formulário
|
||||
const updatedData = {
|
||||
author: document.getElementById('author')?.value || '',
|
||||
authorProfileLink: document.getElementById('authorProfileLink')?.value || '',
|
||||
postLink: document.getElementById('postLink')?.value || '',
|
||||
date: document.getElementById('date')?.value || '',
|
||||
content: document.getElementById('content')?.value || '',
|
||||
category: document.getElementById('category')?.value || ''
|
||||
};
|
||||
|
||||
if (!updatedData.category) {
|
||||
alert('Por favor, selecione uma categoria.');
|
||||
|
||||
document.getElementById('navNewPost')?.addEventListener('click', () => {
|
||||
setActiveNav('navNewPost');
|
||||
});
|
||||
|
||||
// Toggle de senha
|
||||
const togglePasswordBtn = document.getElementById('togglePasswordBtn');
|
||||
togglePasswordBtn?.addEventListener('click', () => {
|
||||
const passwordInput = document.getElementById('password');
|
||||
const type = passwordInput.type === 'password' ? 'text' : 'password';
|
||||
passwordInput.type = type;
|
||||
|
||||
const icon = togglePasswordBtn.querySelector('i');
|
||||
icon.classList.toggle('bi-eye');
|
||||
icon.classList.toggle('bi-eye-slash');
|
||||
});
|
||||
|
||||
function showLoginSection() {
|
||||
loginSection.classList.remove('d-none');
|
||||
loggedInSection.classList.add('d-none');
|
||||
}
|
||||
|
||||
function showLoggedInSection() {
|
||||
loginSection.classList.add('d-none');
|
||||
loggedInSection.classList.remove('d-none');
|
||||
setActiveNav('navSwipeFile'); // Ativa a aba SWIPE FILE por padrão
|
||||
}
|
||||
|
||||
// Gerenciar novo post
|
||||
document.getElementById('savePostButton')?.addEventListener('click', () => {
|
||||
const title = document.getElementById('postTitle').value;
|
||||
const content = tinymce.get('postEditor').getContent();
|
||||
|
||||
if (!title || !content) {
|
||||
alert('Por favor, preencha todos os campos');
|
||||
return;
|
||||
}
|
||||
|
||||
// Desabilitar o botão para evitar múltiplos cliques
|
||||
saveButton.disabled = true;
|
||||
saveButton.innerText = 'Salvando...';
|
||||
|
||||
// Enviar dados para o background script para salvar
|
||||
chrome.runtime.sendMessage({ action: 'savePost', data: updatedData }, (response) => {
|
||||
if (response && response.success) {
|
||||
// Exibir o popup de sucesso
|
||||
showSuccessPopup();
|
||||
} else {
|
||||
// Exibir mensagem de erro
|
||||
alert(response.error || 'Houve um erro ao enviar o post. Por favor, tente novamente.');
|
||||
// Reativar o botão de salvar
|
||||
saveButton.disabled = false;
|
||||
saveButton.innerText = 'Salvar';
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Erro ao preparar os dados para salvar:', error);
|
||||
alert('Ocorreu um erro ao preparar os dados. Por favor, tente novamente.');
|
||||
// Reativar o botão de salvar
|
||||
saveButton.disabled = false;
|
||||
saveButton.innerText = 'Salvar';
|
||||
}
|
||||
});
|
||||
} else {
|
||||
console.error('Elemento saveButton não encontrado.');
|
||||
|
||||
chrome.runtime.sendMessage({
|
||||
action: 'createPost',
|
||||
data: { title, content }
|
||||
}, (response) => {
|
||||
if (response.success) {
|
||||
alert('Post criado com sucesso!');
|
||||
document.getElementById('postTitle').value = '';
|
||||
tinymce.get('postEditor').setContent('');
|
||||
} else {
|
||||
alert(response.error || 'Erro ao criar post');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Carregar dados do post
|
||||
function loadPostData() {
|
||||
chrome.runtime.sendMessage({ action: 'getPostData' }, (response) => {
|
||||
try {
|
||||
if (response && response.data) {
|
||||
const postData = response.data;
|
||||
const fields = ['author', 'authorProfileLink', 'postLink', 'content'];
|
||||
|
||||
fields.forEach(field => {
|
||||
const element = document.getElementById(field);
|
||||
if (element) element.value = postData[field] || '';
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Erro ao carregar dados:', error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Lidar com o clique no botão de logout (se aplicável)
|
||||
|
||||
// Gerenciar formulário de login
|
||||
const loginForm = document.getElementById('loginForm');
|
||||
loginForm?.addEventListener('submit', async (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
const email = document.getElementById('email').value;
|
||||
const password = document.getElementById('password').value;
|
||||
|
||||
chrome.runtime.sendMessage(
|
||||
{
|
||||
action: 'login',
|
||||
data: { email, password }
|
||||
},
|
||||
(response) => {
|
||||
if (response.success) {
|
||||
showLoggedInSection();
|
||||
loadPostData();
|
||||
} else {
|
||||
alert(response.error || 'Erro ao fazer login');
|
||||
}
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
// Gerenciar envio do post
|
||||
document.getElementById('saveSwipeFileButton')?.addEventListener('click', async () => {
|
||||
const content = document.getElementById('content').value;
|
||||
|
||||
// Buscar ID do negócio do storage
|
||||
chrome.storage.local.get(['negocios_id'], async (result) => {
|
||||
if (!result.negocios_id) {
|
||||
alert('Erro ao identificar o negócio.');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await chrome.runtime.sendMessage({
|
||||
action: 'saveSwipeFile',
|
||||
data: {
|
||||
negocio: result.negocios_id,
|
||||
conteudo: content
|
||||
}
|
||||
});
|
||||
|
||||
if (response.success) {
|
||||
alert('Post salvo com sucesso!');
|
||||
window.close();
|
||||
} else {
|
||||
throw new Error(response.error || 'Erro ao salvar o post');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Erro ao salvar:', error);
|
||||
alert('Erro ao salvar o post. Por favor, tente novamente.');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Gerenciar logout
|
||||
const logoutButton = document.getElementById('logoutButton');
|
||||
if (logoutButton) {
|
||||
logoutButton.addEventListener('click', () => {
|
||||
if (confirm('Você realmente deseja fazer logout?')) {
|
||||
chrome.runtime.sendMessage({ action: 'logout' }, (response) => {
|
||||
if (response && response.success) {
|
||||
window.close();
|
||||
} else {
|
||||
alert('Erro ao realizar logout.');
|
||||
logoutButton?.addEventListener('click', () => {
|
||||
chrome.runtime.sendMessage({ action: 'logout' }, (response) => {
|
||||
if (response.success) {
|
||||
showLoginSection();
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// Gerenciar salvamento do post
|
||||
const saveButton = document.getElementById('saveButton');
|
||||
saveButton?.addEventListener('click', () => {
|
||||
const data = {
|
||||
author: document.getElementById('author').value,
|
||||
authorProfileLink: document.getElementById('authorProfileLink').value,
|
||||
postLink: document.getElementById('postLink').value,
|
||||
content: document.getElementById('content').value
|
||||
};
|
||||
|
||||
chrome.runtime.sendMessage({ action: 'savePost', data }, (response) => {
|
||||
if (response.success) {
|
||||
alert('Post salvo com sucesso!');
|
||||
} else {
|
||||
alert(response.error || 'Erro ao salvar o post');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Carregar dados do post atual
|
||||
chrome.storage.local.get(['currentPostData', 'negocios_id'], (result) => {
|
||||
if (result.currentPostData) {
|
||||
document.getElementById('author').value = result.currentPostData.author || '';
|
||||
document.getElementById('content').value = result.currentPostData.content || '';
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
6
postcss.config.js
Normal file
6
postcss.config.js
Normal file
@ -0,0 +1,6 @@
|
||||
module.exports = {
|
||||
plugins: {
|
||||
tailwindcss: {},
|
||||
autoprefixer: {},
|
||||
}
|
||||
}
|
||||
16
src/editor.js
Normal file
16
src/editor.js
Normal file
@ -0,0 +1,16 @@
|
||||
export function initTinyMCE() {
|
||||
tinymce.init({
|
||||
selector: '#postEditor',
|
||||
height: 300,
|
||||
menubar: false,
|
||||
plugins: [
|
||||
'advlist', 'autolink', 'lists', 'link', 'image', 'charmap', 'preview',
|
||||
'anchor', 'searchreplace', 'visualblocks', 'code', 'fullscreen',
|
||||
'insertdatetime', 'media', 'table', 'help', 'wordcount'
|
||||
],
|
||||
toolbar: 'undo redo | blocks | ' +
|
||||
'bold italic backcolor | alignleft aligncenter ' +
|
||||
'alignright alignjustify | bullist numlist outdent indent | ' +
|
||||
'removeformat | help'
|
||||
});
|
||||
}
|
||||
345
styles.css
345
styles.css
@ -1,103 +1,252 @@
|
||||
/* Estilos gerais */
|
||||
:root {
|
||||
--primary: #4F46E5;
|
||||
--primary-hover: #4338CA;
|
||||
--gray-900: #111827;
|
||||
--gray-800: #1F2937;
|
||||
--gray-700: #374151;
|
||||
--gray-600: #4B5563;
|
||||
--gray-500: #6B7280;
|
||||
--gray-400: #9CA3AF;
|
||||
--gray-300: #D1D5DB;
|
||||
--gray-200: #E5E7EB;
|
||||
--gray-100: #F3F4F6;
|
||||
--gray-50: #F9FAFB;
|
||||
--white: #FFFFFF;
|
||||
--error: #EF4444;
|
||||
--success: #10B981;
|
||||
}
|
||||
|
||||
/* Reset e estilos base */
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: Arial, sans-serif;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
width: 400px;
|
||||
min-height: 500px;
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
||||
background: var(--white);
|
||||
color: var(--gray-900);
|
||||
}
|
||||
|
||||
/* Layout */
|
||||
.container {
|
||||
padding: 2rem;
|
||||
}
|
||||
|
||||
.header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
.logo {
|
||||
height: 2rem;
|
||||
width: auto;
|
||||
}
|
||||
|
||||
.version {
|
||||
margin-left: 0.5rem;
|
||||
font-size: 0.875rem;
|
||||
color: var(--gray-500);
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: 1.875rem;
|
||||
font-weight: 700;
|
||||
color: var(--primary);
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
/* Formulários */
|
||||
.form-group {
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
.label {
|
||||
display: block;
|
||||
font-size: 1.125rem;
|
||||
font-weight: 500;
|
||||
color: var(--gray-700);
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.input-with-icon {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.input {
|
||||
width: 100%;
|
||||
padding: 0.625rem 1rem;
|
||||
padding-left: 2.5rem;
|
||||
border: 1px solid var(--gray-300);
|
||||
border-radius: 0.5rem;
|
||||
font-size: 1rem;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.input:focus {
|
||||
outline: none;
|
||||
border-color: var(--primary);
|
||||
box-shadow: 0 0 0 2px rgba(79, 70, 229, 0.1);
|
||||
}
|
||||
|
||||
.input-icon {
|
||||
position: absolute;
|
||||
left: 0.75rem;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
color: var(--gray-400);
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.icon {
|
||||
width: 1.25rem;
|
||||
height: 1.25rem;
|
||||
}
|
||||
|
||||
/* Botões */
|
||||
.btn {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 0.625rem 1rem;
|
||||
border-radius: 0.5rem;
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
width: 100%;
|
||||
background-color: var(--primary);
|
||||
color: var(--white);
|
||||
font-size: 1.125rem;
|
||||
font-weight: 600;
|
||||
padding: 0.75rem;
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
.btn-primary:hover {
|
||||
background-color: var(--primary-hover);
|
||||
}
|
||||
|
||||
.btn-primary:focus {
|
||||
outline: none;
|
||||
box-shadow: 0 0 0 3px rgba(79, 70, 229, 0.5);
|
||||
}
|
||||
|
||||
.btn-icon {
|
||||
position: absolute;
|
||||
right: 0.75rem;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
background: none;
|
||||
border: none;
|
||||
color: var(--gray-400);
|
||||
cursor: pointer;
|
||||
padding: 0.25rem;
|
||||
}
|
||||
|
||||
.btn-icon:hover {
|
||||
color: var(--gray-600);
|
||||
}
|
||||
|
||||
/* Links */
|
||||
.link {
|
||||
color: var(--primary);
|
||||
text-decoration: none;
|
||||
font-size: 0.875rem;
|
||||
transition: color 0.2s;
|
||||
display: inline-block;
|
||||
margin-top: 0.5rem;
|
||||
}
|
||||
|
||||
.link:hover {
|
||||
color: var(--primary-hover);
|
||||
}
|
||||
|
||||
/* Footer */
|
||||
.footer {
|
||||
text-align: center;
|
||||
margin-top: 1.5rem;
|
||||
color: var(--gray-600);
|
||||
}
|
||||
|
||||
/* Navegação */
|
||||
.nav {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 1rem;
|
||||
background-color: var(--white);
|
||||
border-bottom: 1px solid var(--gray-200);
|
||||
}
|
||||
|
||||
.nav-item {
|
||||
padding: 0.5rem 1rem;
|
||||
color: var(--gray-700);
|
||||
text-decoration: none;
|
||||
border-bottom: 2px solid transparent;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.nav-item.active {
|
||||
color: var(--primary);
|
||||
border-bottom-color: var(--primary);
|
||||
}
|
||||
|
||||
/* Utilitários */
|
||||
.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.flex {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.items-center {
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.justify-between {
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
/* Estados */
|
||||
.disabled {
|
||||
opacity: 0.7;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
/* Área logada */
|
||||
#loggedInSection {
|
||||
background-color: var(--gray-50);
|
||||
}
|
||||
|
||||
/* Formulário de post */
|
||||
.post-form {
|
||||
background: var(--white);
|
||||
padding: 1.5rem;
|
||||
border-radius: 0.5rem;
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
/* Campos somente leitura */
|
||||
.input[readonly] {
|
||||
background-color: var(--gray-50);
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
/* Responsividade */
|
||||
@media (max-width: 400px) {
|
||||
body {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
/* Container principal */
|
||||
.container {
|
||||
padding: 20px;
|
||||
width: 300px;
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
/* Títulos */
|
||||
h2 {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
/* Formulários */
|
||||
form label {
|
||||
display: block;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
form input[type="text"],
|
||||
form select,
|
||||
form textarea {
|
||||
width: 100%;
|
||||
padding: 5px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
form button {
|
||||
margin-top: 15px;
|
||||
width: 100%;
|
||||
padding: 10px;
|
||||
background-color: #0073b1;
|
||||
color: #fff;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
form button:hover {
|
||||
background-color: #005582;
|
||||
}
|
||||
|
||||
/* Estilos para o popup */
|
||||
.popup {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
z-index: 999;
|
||||
}
|
||||
|
||||
.popup-content {
|
||||
background-color: #fff;
|
||||
padding: 20px;
|
||||
border-radius: 5px;
|
||||
text-align: center;
|
||||
width: 80%;
|
||||
max-width: 300px;
|
||||
}
|
||||
|
||||
.popup-content p {
|
||||
font-size: 16px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
/* Botões do popup */
|
||||
.popup-buttons {
|
||||
display: flex;
|
||||
justify-content: space-around;
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.popup-buttons button {
|
||||
padding: 10px 20px;
|
||||
font-size: 16px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
#cancelButton {
|
||||
background-color: #ccc;
|
||||
border: none;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
#viewButton {
|
||||
background-color: #0073b1;
|
||||
color: #fff;
|
||||
border: none;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
#viewButton:hover {
|
||||
background-color: #005582;
|
||||
}
|
||||
}
|
||||
27
styles/input.css
Normal file
27
styles/input.css
Normal file
@ -0,0 +1,27 @@
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
/* Estilos base personalizados */
|
||||
@layer base {
|
||||
input[type="email"],
|
||||
input[type="password"],
|
||||
input[type="text"] {
|
||||
@apply w-full rounded-lg border-gray-300;
|
||||
}
|
||||
|
||||
button {
|
||||
@apply transition-colors duration-200;
|
||||
}
|
||||
}
|
||||
|
||||
/* Componentes personalizados */
|
||||
@layer components {
|
||||
.input-icon {
|
||||
@apply absolute inset-y-0 left-3 flex items-center pointer-events-none text-gray-400;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
@apply w-full bg-indigo-600 text-white py-2 px-4 rounded-lg hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2 text-lg font-semibold;
|
||||
}
|
||||
}
|
||||
@ -1,10 +1,10 @@
|
||||
/** @type {import('tailwindcss').Config} */
|
||||
module.exports = {
|
||||
content: [
|
||||
"./src/**/*.{html,js,ts,jsx,tsx}",
|
||||
"./*.html"
|
||||
],
|
||||
content: ["./**/*.{html,js}"],
|
||||
theme: {
|
||||
extend: {},
|
||||
},
|
||||
plugins: [
|
||||
require('@tailwindcss/forms'),
|
||||
],
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user