Add trending keywords app with opportunity scoring algorithm

This commit is contained in:
Imtiaz Ali
2025-10-29 22:38:46 +03:00
parent 784f9471a7
commit 448f013989
6 changed files with 951 additions and 0 deletions

145
public/app.js Normal file
View File

@@ -0,0 +1,145 @@
document.addEventListener('DOMContentLoaded', () => {
// DOM Elements
const keywordsContainer = document.getElementById('keywords-container');
const refreshBtn = document.getElementById('refresh-btn');
const loading = document.getElementById('loading');
const selectedKeywordText = document.getElementById('selected-keyword-text');
const generatePromptBtn = document.getElementById('generate-prompt-btn');
const promptResult = document.getElementById('prompt-result');
const promptTitle = document.getElementById('prompt-title');
const promptBody = document.getElementById('prompt-body');
const copyPromptBtn = document.getElementById('copy-prompt-btn');
const copySuccess = document.getElementById('copy-success');
// State
let selectedKeyword = null;
let currentPrompt = null;
// Fetch trending keywords
const fetchTrendingKeywords = async () => {
try {
loading.classList.remove('hidden');
keywordsContainer.innerHTML = '';
const response = await fetch('/api/trending-keywords');
const data = await response.json();
if (data.keywords && data.keywords.length > 0) {
renderKeywords(data.keywords);
} else {
keywordsContainer.innerHTML = '<p>No trending keywords found. Please try again later.</p>';
}
} catch (error) {
console.error('Error fetching trending keywords:', error);
keywordsContainer.innerHTML = '<p>Failed to load trending keywords. Please try again.</p>';
} finally {
loading.classList.add('hidden');
}
};
// Render keywords
const renderKeywords = (keywords) => {
keywordsContainer.innerHTML = '';
keywords.forEach(keyword => {
const keywordCard = document.createElement('div');
keywordCard.className = 'keyword-card';
keywordCard.dataset.keyword = keyword.keyword;
keywordCard.innerHTML = `
<div class="keyword-name">${keyword.keyword}</div>
<div class="keyword-score">Trend score: ${keyword.score}</div>
`;
keywordCard.addEventListener('click', () => selectKeyword(keyword.keyword, keywordCard));
keywordsContainer.appendChild(keywordCard);
});
};
// Select a keyword
const selectKeyword = (keyword, card) => {
// Remove selected class from all cards
document.querySelectorAll('.keyword-card').forEach(k => k.classList.remove('selected'));
// Add selected class to clicked card
card.classList.add('selected');
// Update selected keyword
selectedKeyword = keyword;
selectedKeywordText.textContent = keyword;
// Enable generate prompt button
generatePromptBtn.disabled = false;
// Hide prompt result if visible
promptResult.classList.add('hidden');
};
// Generate article prompt
const generatePrompt = async () => {
if (!selectedKeyword) return;
try {
loading.classList.remove('hidden');
generatePromptBtn.disabled = true;
const response = await fetch('/api/generate-prompt', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ keyword: selectedKeyword }),
});
const data = await response.json();
if (data.prompt) {
currentPrompt = data.prompt;
displayPrompt(data.prompt);
} else {
alert('Failed to generate prompt. Please try again.');
}
} catch (error) {
console.error('Error generating prompt:', error);
alert('Failed to generate prompt. Please try again.');
} finally {
loading.classList.add('hidden');
generatePromptBtn.disabled = false;
}
};
// Display the generated prompt
const displayPrompt = (prompt) => {
promptTitle.textContent = prompt.title;
promptBody.textContent = prompt.content;
promptResult.classList.remove('hidden');
};
// Copy prompt to clipboard
const copyPrompt = () => {
if (!currentPrompt) return;
const fullPrompt = `${currentPrompt.title}\n\n${currentPrompt.content}`;
navigator.clipboard.writeText(fullPrompt)
.then(() => {
copySuccess.classList.remove('hidden');
setTimeout(() => {
copySuccess.classList.add('hidden');
}, 2000);
})
.catch(err => {
console.error('Failed to copy prompt:', err);
alert('Failed to copy prompt to clipboard');
});
};
// Event listeners
refreshBtn.addEventListener('click', fetchTrendingKeywords);
generatePromptBtn.addEventListener('click', generatePrompt);
copyPromptBtn.addEventListener('click', copyPrompt);
// Initial load
fetchTrendingKeywords();
});

57
public/index.html Normal file
View File

@@ -0,0 +1,57 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Trending Keywords & Article Prompts</title>
<link rel="stylesheet" href="styles.css">
<link href="https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;500;600;700&display=swap" rel="stylesheet">
</head>
<body>
<div class="container">
<header>
<h1>Trending Keywords</h1>
<p>Discover globally trending topics and generate article prompts</p>
</header>
<main>
<section class="trending-section">
<h2>Today's Trending Keywords</h2>
<div class="refresh-container">
<button id="refresh-btn">Refresh Keywords</button>
<div id="loading" class="loading hidden">Loading...</div>
</div>
<div id="keywords-container" class="keywords-container">
<!-- Keywords will be populated here -->
</div>
</section>
<section class="prompt-section">
<h2>Article Prompt Generator</h2>
<div class="selected-keyword">
<p>Selected keyword: <span id="selected-keyword-text">None selected</span></p>
</div>
<button id="generate-prompt-btn" disabled>Generate Article Prompt</button>
<div id="prompt-result" class="prompt-result hidden">
<h3>Your ChatGPT Prompt</h3>
<div class="prompt-content">
<h4 id="prompt-title"></h4>
<div id="prompt-body"></div>
<div class="copy-container">
<button id="copy-prompt-btn">Copy Prompt</button>
<span id="copy-success" class="copy-success hidden">Copied!</span>
</div>
</div>
</div>
</section>
</main>
<footer>
<p>&copy; 2023 Trending Keywords App</p>
</footer>
</div>
<script src="app.js"></script>
</body>
</html>

182
public/styles.css Normal file
View File

@@ -0,0 +1,182 @@
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Poppins', sans-serif;
background-color: #f5f7fa;
color: #333;
line-height: 1.6;
}
.container {
max-width: 1200px;
margin: 0 auto;
padding: 20px;
}
header {
text-align: center;
margin-bottom: 40px;
padding: 20px 0;
background: linear-gradient(135deg, #6e8efb, #a777e3);
color: white;
border-radius: 10px;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
}
header h1 {
font-size: 2.5rem;
margin-bottom: 10px;
}
header p {
font-size: 1.1rem;
opacity: 0.9;
}
section {
background: white;
border-radius: 10px;
padding: 25px;
margin-bottom: 30px;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.05);
}
h2 {
margin-bottom: 20px;
color: #444;
font-weight: 600;
}
.refresh-container {
display: flex;
align-items: center;
margin-bottom: 20px;
}
button {
background-color: #6e8efb;
color: white;
border: none;
padding: 10px 20px;
border-radius: 5px;
cursor: pointer;
font-size: 1rem;
font-weight: 500;
transition: background-color 0.3s;
}
button:hover {
background-color: #5a7df7;
}
button:disabled {
background-color: #cccccc;
cursor: not-allowed;
}
.loading {
margin-left: 15px;
color: #666;
}
.keywords-container {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
gap: 15px;
}
.keyword-card {
background: #f9f9f9;
border: 1px solid #eee;
border-radius: 8px;
padding: 15px;
cursor: pointer;
transition: transform 0.2s, box-shadow 0.2s;
}
.keyword-card:hover {
transform: translateY(-3px);
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.1);
}
.keyword-card.selected {
border: 2px solid #6e8efb;
background-color: #f0f4ff;
}
.keyword-name {
font-weight: 500;
margin-bottom: 5px;
}
.keyword-score {
font-size: 0.9rem;
color: #888;
}
.selected-keyword {
margin-bottom: 20px;
padding: 10px;
background-color: #f0f4ff;
border-radius: 5px;
}
.prompt-result {
margin-top: 30px;
border: 1px solid #eee;
border-radius: 8px;
padding: 20px;
}
.prompt-content {
background-color: #f9f9f9;
padding: 20px;
border-radius: 5px;
margin-top: 15px;
}
#prompt-title {
margin-bottom: 15px;
color: #333;
}
#prompt-body {
white-space: pre-wrap;
line-height: 1.7;
}
.copy-container {
margin-top: 20px;
display: flex;
align-items: center;
}
.copy-success {
margin-left: 10px;
color: #4CAF50;
}
footer {
text-align: center;
margin-top: 40px;
padding: 20px 0;
color: #777;
}
.hidden {
display: none;
}
@media (max-width: 768px) {
.keywords-container {
grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
}
header h1 {
font-size: 2rem;
}
}