← back to Designer Wallcoverings
DW-Agents/dw-agents/agent-new-client-signup/new-client-signup-agent-v1.0.ts
437 lines
/**
* DW-Agents: New Client Signup Monitor
*
* Monitors #new-client-signup Slack channel for customer signups
* Auto-refreshes every 5 seconds
* Port: 9890
*/
import express, { Request, Response } from 'express';
import { requireAuth as ssoAuth, handleLogin, setSSOToken, SSO_TOKEN_NAME, SSO_TOKEN_VALUE } from './shared-auth';
import cookieParser from 'cookie-parser';
import session from 'express-session';
import { WebClient } from '@slack/web-api';
import { fetchRecentSignups, getSignupStats, CustomerSignup } from './shopify-customers';
const app = express();
const PORT = 9890;
// Slack configuration
const SLACK_TOKEN = process.env.SLACK_BOT_TOKEN || 'xoxb-your-token';
const SLACK_CHANNEL = 'new-client-signup';
const slack = new WebClient(SLACK_TOKEN);
app.use(express.json());
app.use(cookieParser());
app.use(express.urlencoded({ extended: true }));
declare module 'express-session' {
interface SessionData {
authenticated: boolean;
}
}
app.use(
session({
secret: 'dw-signup-monitor-2025',
resave: false,
saveUninitialized: true,
cookie: {
secure: false,
httpOnly: true,
maxAge: 7 * 24 * 60 * 60 * 1000,
},
})
);
const requireAuth = (req: Request, res: Response, next: any) => {
if (req.session.authenticated) {
return next();
}
if (req.cookies && req.cookies[SSO_TOKEN_NAME] === SSO_TOKEN_VALUE) {
req.session.authenticated = true;
return next();
}
res.redirect('/login');
};
// Login page
app.get('/login', (req: Request, res: Response) => {
res.send(`
<!DOCTYPE html>
<html>
<head>
<title>New Client Signups - Login</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
background: linear-gradient(135deg, #7C3AED 0%, #A78BFA 100%);
display: flex;
align-items: center;
justify-content: center;
min-height: 100vh;
}
.login-container {
background: white;
padding: 40px;
border-radius: 15px;
box-shadow: 0 15px 35px rgba(0,0,0,0.2);
max-width: 400px;
width: 100%;
}
h1 { color: #7C3AED; text-align: center; margin-bottom: 30px; }
input {
width: 100%;
padding: 12px;
margin: 10px 0;
border: 2px solid #e0e0e0;
border-radius: 8px;
}
button {
width: 100%;
background: linear-gradient(135deg, #7C3AED 0%, #A78BFA 100%);
color: white;
border: none;
padding: 15px;
border-radius: 8px;
cursor: pointer;
font-weight: 600;
margin-top: 10px;
}
</style>
</head>
<body>
<div class="login-container">
<h1>🎯 New Client Signups</h1>
<form method="POST">
<input type="text" name="username" placeholder="Username" required>
<input type="password" name="password" placeholder="Password" required>
<button type="submit">Login</button>
</form>
</div>
</body>
</html>
`);
});
app.post('/login', (req: Request, res: Response) => {
const { username, password } = req.body;
if (username === 'admin' && password === '2025') {
req.session.authenticated = true;
setSSOToken(res);
res.redirect('/');
} else {
res.redirect('/login');
}
});
// Main dashboard
app.get('/', requireAuth, (req: Request, res: Response) => {
res.send(`
<!DOCTYPE html>
<html>
<head>
<title>New Client Signups - DW-Agents</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
background: linear-gradient(135deg, #7C3AED 0%, #A78BFA 100%);
padding: 20px;
min-height: 100vh;
}
.header {
background: white;
padding: 30px;
border-radius: 15px;
margin-bottom: 20px;
box-shadow: 0 5px 20px rgba(0,0,0,0.1);
}
.header h1 { color: #7C3AED; font-size: 2.5em; margin-bottom: 10px; }
.header .subtitle { color: #666; font-size: 1.1em; }
.stats-bar {
background: white;
padding: 20px 30px;
border-radius: 15px;
margin-bottom: 20px;
box-shadow: 0 5px 20px rgba(0,0,0,0.1);
display: grid;
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
gap: 20px;
}
.stat-item {
text-align: center;
}
.stat-number {
font-size: 2.5em;
font-weight: bold;
color: #7C3AED;
}
.stat-label {
color: #666;
font-size: 0.9em;
margin-top: 5px;
}
.refresh-indicator {
background: white;
padding: 15px 30px;
border-radius: 15px;
margin-bottom: 20px;
display: flex;
justify-content: space-between;
align-items: center;
box-shadow: 0 5px 20px rgba(0,0,0,0.1);
}
.refresh-indicator .pulse {
width: 12px;
height: 12px;
background: #10B981;
border-radius: 50%;
animation: pulse 2s infinite;
margin-right: 10px;
}
@keyframes pulse {
0%, 100% { opacity: 1; transform: scale(1); }
50% { opacity: 0.5; transform: scale(1.2); }
}
.signups-container {
background: white;
padding: 25px;
border-radius: 15px;
box-shadow: 0 5px 20px rgba(0,0,0,0.1);
overflow-x: auto;
}
.signups-container h2 {
color: #7C3AED;
margin-bottom: 20px;
font-size: 1.5em;
}
table {
width: 100%;
border-collapse: collapse;
font-size: 0.9em;
}
thead {
background: #7C3AED;
color: white;
position: sticky;
top: 0;
z-index: 10;
}
th {
padding: 12px 8px;
text-align: left;
font-weight: 600;
border-right: 1px solid rgba(255,255,255,0.2);
}
th:last-child {
border-right: none;
}
td {
padding: 10px 8px;
border-bottom: 1px solid #e0e0e0;
vertical-align: top;
}
tr:hover {
background: #f8f9fa;
}
tr.new {
animation: highlightRow 2s ease-out;
}
@keyframes highlightRow {
from { background: #FEF3C7; }
to { background: white; }
}
.trade-badge {
background: #10B981;
color: white;
padding: 2px 8px;
border-radius: 8px;
font-size: 0.8em;
font-weight: 600;
}
.shopify-link {
display: inline-block;
background: #7C3AED;
color: white;
padding: 6px 12px;
border-radius: 6px;
text-decoration: none;
font-weight: 600;
font-size: 0.85em;
transition: all 0.2s;
}
.shopify-link:hover {
background: #6D28D9;
transform: translateY(-1px);
box-shadow: 0 2px 8px rgba(124, 58, 237, 0.3);
}
.no-signups {
text-align: center;
color: #888;
padding: 40px;
font-size: 1.1em;
}
</style>
</head>
<body>
<div class="header">
<h1>🎯 New Client Signups</h1>
<div class="subtitle">Real-time Shopify customer data • Auto-refreshing every 5 seconds</div>
<div style="margin-top: 10px; padding: 10px; background: #10B981; color: white; border-radius: 8px; text-align: center; font-weight: 600;">
✅ LIVE SHOPIFY DATA - NO MOCK DATA
</div>
<div style="margin-top: 15px; text-align: center;">
<a href="https://admin.shopify.com/store/designer-laboratory-sandbox/customers" target="_blank" style="display: inline-block; padding: 12px 24px; background: #7C3AED; color: white; text-decoration: none; border-radius: 8px; font-weight: 600; box-shadow: 0 3px 10px rgba(124, 58, 237, 0.3); transition: all 0.2s;">
🛍️ View All Clients in Shopify
</a>
</div>
</div>
<div class="stats-bar">
<div class="stat-item">
<div class="stat-number" id="todayCount">0</div>
<div class="stat-label">Today</div>
</div>
<div class="stat-item">
<div class="stat-number" id="thisWeekCount">0</div>
<div class="stat-label">This Week</div>
</div>
<div class="stat-item">
<div class="stat-number" id="thisMonthCount">0</div>
<div class="stat-label">This Month</div>
</div>
</div>
<div class="refresh-indicator">
<div style="display: flex; align-items: center;">
<div class="pulse"></div>
<span style="color: #888;" id="lastUpdate">Auto-refreshing every 5 seconds...</span>
</div>
<span style="color: #10B981; font-weight: 600;" id="nextRefresh">Next: 5s</span>
</div>
<div class="signups-container">
<h2>Recent Signups</h2>
<div id="signupsList" class="no-signups">Loading signups...</div>
</div>
<script>
let previousSignups = [];
let countdown = 5;
async function refreshSignups() {
try {
const data = await fetch('/api/signups').then(r => r.json());
// Update stats
document.getElementById('todayCount').textContent = data.stats.today;
document.getElementById('thisWeekCount').textContent = data.stats.thisWeek;
document.getElementById('thisMonthCount').textContent = data.stats.thisMonth;
// Update signups list
const signupsList = document.getElementById('signupsList');
if (data.signups.length > 0) {
const tableHTML = \`
<table>
<thead>
<tr>
<th>Date</th>
<th>Name</th>
<th>Email</th>
<th>Company</th>
<th>Location</th>
<th>Orders</th>
<th>Spent</th>
<th>Type</th>
<th>Shopify</th>
</tr>
</thead>
<tbody>
\${data.signups.map(signup => {
const isNew = !previousSignups.some(s => s.timestamp === signup.timestamp);
const location = (signup.location || '').split(',');
const city = location[0] || 'N/A';
const state = location[1] || '';
const isTrade = (signup.tags || '').includes('trade') || (signup.tags || '').includes('Interior Designer') || (signup.tags || '').includes('Contractor') || (signup.tags || '').includes('Architect');
return \`
<tr class="\${isNew ? 'new' : ''}">
<td>\${new Date(signup.timestamp).toLocaleDateString()}</td>
<td><strong>\${signup.customerName || 'N/A'}</strong></td>
<td>\${signup.email || 'N/A'}</td>
<td>\${signup.company || 'Individual'}</td>
<td>\${city}\${state ? ', ' + state : ''}</td>
<td>\${signup.ordersCount || 0}</td>
<td>$\${signup.totalSpent || '0'}</td>
<td>\${isTrade ? '<span class="trade-badge">Trade</span>' : 'Customer'}</td>
<td>\${signup.id ? '<a href="https://admin.shopify.com/store/designer-laboratory-sandbox/customers/' + signup.id + '" target="_blank" class="shopify-link">View Client</a>' : 'N/A'}</td>
</tr>
\`;
}).join('')}
</tbody>
</table>
\`;
signupsList.innerHTML = tableHTML;
previousSignups = data.signups;
} else {
signupsList.innerHTML = '<div class="no-signups">No signups yet today</div>';
}
document.getElementById('lastUpdate').textContent = 'Last updated: ' + new Date().toLocaleTimeString();
countdown = 5;
} catch (error) {
console.error('Error fetching signups:', error);
}
}
// Countdown timer
setInterval(() => {
countdown--;
if (countdown <= 0) {
countdown = 5;
}
document.getElementById('nextRefresh').textContent = 'Next: ' + countdown + 's';
}, 1000);
// Auto-refresh every 5 seconds
setInterval(refreshSignups, 5000);
refreshSignups();
</script>
</body>
</html>
`);
});
// API endpoint to get signups - REAL SHOPIFY DATA
app.get('/api/signups', requireAuth, async (req: Request, res: Response) => {
try {
console.log('📊 Fetching real Shopify customer signups...');
// Fetch real signups from Shopify - last month of customers
const signups = await fetchRecentSignups(200);
const stats = getSignupStats(signups);
console.log(`✅ Returning ${signups.length} real signups from Shopify`);
console.log(` Today: ${stats.today}, Week: ${stats.thisWeek}, Month: ${stats.thisMonth}`);
res.json({
signups: signups,
stats,
generatedAt: new Date(),
dataSource: 'Shopify API (REAL DATA)'
});
} catch (error) {
console.error('❌ Error fetching signups:', error);
res.status(500).json({ error: 'Failed to fetch signups' });
}
});
app.listen(PORT, '0.0.0.0', () => {
console.log(`🎯 New Client Signup Monitor running on port ${PORT}`);
console.log(`Dashboard: http://45.61.58.125:${PORT}`);
});