← back to NationalPaperHangers
views/admin/dashboard.ejs
178 lines
<%- include('../partials/head', { title, admin: true }) %>
<%- include('partials/admin-header') %>
<section class="admin-main">
<%
/* Onboarding completion signals derived from available render data */
var _profileComplete = !!installer.profile_complete;
var _availabilitySet = (typeof hasAvailability !== 'undefined') ? !!hasAvailability : false;
var _proActive = installer.subscription_status === 'active';
var _payoutsActive = !!installer.stripe_account_id && !!installer.stripe_account_charges_enabled;
var _allDone = _profileComplete && _availabilitySet && _proActive && _payoutsActive;
%>
<% if (typeof connectFlash !== 'undefined' && connectFlash === 'ok') { %>
<div class="callout callout-ok"><strong>Payouts ready.</strong> Stripe Connect onboarding complete — booking deposits will route to your bank account, net of NPH's <%= ((installer.tier === 'enterprise') ? '15' : '25') %>% lead-coordination fee.</div>
<% } else if (typeof connectFlash !== 'undefined' && connectFlash === 'mock') { %>
<div class="callout">Stripe is in test mode — Connect onboarding ran in mock. Save real <code>STRIPE_SECRET_KEY</code> via the secrets manager to enable live payouts.</div>
<% } %>
<% if (flash && flash.connect_prompt && !installer.stripe_account_id) { %>
<aside class="callout callout-prompt" role="region" aria-label="Set up payouts">
<p style="margin:0 0 6px"><strong>Welcome — your studio is claimed. One last setup step.</strong></p>
<p style="margin:0 0 12px;font-size:0.92rem">Connect your bank account so booking deposits flow to you automatically. Takes 3 minutes — Stripe-hosted, no NPH access to your bank info.</p>
<form method="post" action="/admin/connect/onboard" class="inline-form" style="margin:0">
<input type="hidden" name="_csrf" value="<%= csrfToken %>">
<button type="submit" class="btn btn-primary">Set up payouts now →</button>
</form>
<p style="margin:8px 0 0;font-size:0.82rem;color:var(--fg-muted)">You can skip and do this later from /admin/billing.</p>
</aside>
<% } %>
<% if (welcome || !_allDone) { %>
<div class="onboarding-checklist" aria-label="Account setup checklist">
<h2 class="onboarding-title">Get your studio live</h2>
<ol class="onboarding-steps" role="list">
<li class="onboarding-step <%= _profileComplete ? 'step-done' : 'step-todo' %>">
<span class="step-icon" aria-hidden="true"><%= _profileComplete ? '✓' : '○' %></span>
<span class="step-label">Profile complete</span>
<% if (!_profileComplete) { %>
<a href="/admin/profile" class="step-action">Complete profile →</a>
<% } else { %>
<span class="step-done-label">Done</span>
<% } %>
</li>
<li class="onboarding-step <%= _availabilitySet ? 'step-done' : 'step-todo' %>">
<span class="step-icon" aria-hidden="true"><%= _availabilitySet ? '✓' : '○' %></span>
<span class="step-label">Availability set</span>
<% if (!_availabilitySet) { %>
<a href="/admin/calendar" class="step-action">Set availability →</a>
<% } else { %>
<span class="step-done-label">Done</span>
<% } %>
</li>
<li class="onboarding-step <%= _proActive ? 'step-done' : 'step-todo' %>">
<span class="step-icon" aria-hidden="true"><%= _proActive ? '✓' : '○' %></span>
<span class="step-label">Pro tier active</span>
<% if (!_proActive) { %>
<a href="/admin/billing" class="step-action btn btn-primary btn-sm">Upgrade to Pro →</a>
<% } else { %>
<span class="step-done-label">Active</span>
<% } %>
</li>
<li class="onboarding-step <%= _payoutsActive ? 'step-done' : 'step-todo' %>">
<span class="step-icon" aria-hidden="true"><%= _payoutsActive ? '✓' : '○' %></span>
<span class="step-label">Payouts set up</span>
<% if (!_payoutsActive) { %>
<form method="post" action="/admin/connect/onboard" class="inline-form" style="margin:0">
<input type="hidden" name="_csrf" value="<%= csrfToken %>">
<button type="submit" class="step-action btn btn-primary btn-sm">Set up payouts →</button>
</form>
<% } else { %>
<span class="step-done-label">Active</span>
<% } %>
</li>
</ol>
<% if (!_payoutsActive) { %>
<p class="muted" style="margin: 12px 0 0; font-size: 0.85rem;">
Customers pay their booking deposit through NPH. Once your Stripe payouts are set up, your share lands in your bank in 1–2 business days, net of NPH's <%= Math.round((installer.tier === 'enterprise' ? 1500 : 2500) / 100) %>% lead-coordination fee.
</p>
<% } %>
</div>
<% } %>
<header class="admin-page-head">
<h1>Dashboard</h1>
<p class="muted"><%= installer.business_name %> · <%= installer.city %>, <%= installer.state %> · <%= installer.tier %> tier</p>
</header>
<div class="stat-grid">
<div class="stat">
<span class="stat-label">Pending</span>
<span class="stat-value"><%= stats.pending_count %></span>
</div>
<div class="stat">
<span class="stat-label">Upcoming</span>
<span class="stat-value"><%= stats.upcoming_count %></span>
</div>
<div class="stat">
<span class="stat-label">Completed</span>
<span class="stat-value"><%= stats.completed_count %></span>
</div>
<div class="stat">
<span class="stat-label">Portfolio items</span>
<span class="stat-value"><%= portfolioCount %></span>
</div>
<% if (typeof market !== 'undefined' && market) { %>
<div class="stat">
<span class="stat-label">Deposits paid</span>
<span class="stat-value">$<%= (market.deposits_paid_cents/100).toFixed(0) %></span>
<span class="stat-sub muted"><%= market.count_paid %> bookings</span>
</div>
<% if (!market.on_connect && market.deposits_paid_cents > 0) { %>
<div class="stat stat-warn">
<span class="stat-label">Pending payout</span>
<span class="stat-value">$<%= (market.payouts_pending_cents/100).toFixed(0) %></span>
<span class="stat-sub muted">held — set up payouts</span>
</div>
<% } %>
<% } %>
</div>
<% if (installer.subscription_status !== 'active') { %>
<div class="callout callout-warn">
<strong>Calendar bookings are off.</strong>
Upgrade to Pro to let customers self-book directly into your calendar.
<a href="/admin/billing" class="btn btn-primary btn-sm">Upgrade</a>
</div>
<% } %>
<section class="admin-section">
<div class="section-head">
<h2>Upcoming bookings</h2>
<a href="/admin/bookings" class="section-link">All bookings →</a>
</div>
<% if (upcoming.length === 0) { %>
<div class="empty-state">No upcoming bookings yet.</div>
<% } else { %>
<table class="data-table">
<thead><tr><th>When</th><th>Customer</th><th>Type</th><th>Status</th></tr></thead>
<tbody>
<% upcoming.forEach(function(b){ %>
<tr>
<td><%= new Date(b.scheduled_start).toLocaleString() %></td>
<td><%= b.customer_name %><br><span class="muted"><%= b.customer_email %></span></td>
<td><%= (b.project_type || 'consultation').replace(/_/g,' ') %></td>
<td><span class="status-badge status-<%= b.status %>"><%= b.status %></span></td>
</tr>
<% }); %>
</tbody>
</table>
<% } %>
</section>
<section class="admin-section">
<div class="section-head"><h2>Quick actions</h2></div>
<div class="action-grid">
<a class="action-card" href="/admin/calendar">
<h3>Calendar</h3>
<p>Set weekly availability and block time off.</p>
</a>
<a class="action-card" href="/admin/profile">
<h3>Profile</h3>
<p>Edit your studio bio, materials, brands, and accreditations.</p>
</a>
<a class="action-card" href="/admin/bookings">
<h3>Bookings</h3>
<p>Review, confirm, decline, or complete jobs.</p>
</a>
<a class="action-card" href="/admin/billing">
<h3>Billing</h3>
<p>Manage your subscription and invoices.</p>
</a>
</div>
</section>
</section>
<%- include('../partials/footer') %>