Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .changeset/sixty-colts-bathe.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
---
---

Add company type and revenue tier collection during onboarding flow.
74 changes: 61 additions & 13 deletions server/public/admin-members.html
Original file line number Diff line number Diff line change
Expand Up @@ -368,16 +368,17 @@ <h1>Member Management</h1>
<thead>
<tr>
<th>Company</th>
<th>Type</th>
<th>Revenue</th>
<th>Owner Email</th>
<th>Subscription</th>
<th>Amount</th>
<th>Renewal Date</th>
<th>Agreement</th>
<th>Actions</th>
</tr>
</thead>
<tbody id="membersTableBody">
<tr><td colspan="7" style="text-align: center; padding: var(--space-10); color: var(--color-text-secondary);">No members found</td></tr>
<tr><td colspan="8" style="text-align: center; padding: var(--space-10); color: var(--color-text-secondary);">No members found</td></tr>
</tbody>
</table>
</div>
Expand Down Expand Up @@ -511,14 +512,39 @@ <h4>This action cannot be undone</h4>
countDiv.textContent = `Showing ${filteredMembers.length} of ${allMembers.length} members`;

if (filteredMembers.length === 0) {
tbody.innerHTML = '<tr><td colspan="7" style="text-align: center; padding: var(--space-10); color: var(--color-text-secondary);">No members found</td></tr>';
tbody.innerHTML = '<tr><td colspan="8" style="text-align: center; padding: var(--space-10); color: var(--color-text-secondary);">No members found</td></tr>';
return;
}

tbody.innerHTML = filteredMembers.map(member => {
const statusClass = `status-${member.subscription_status || 'none'}`.replace('_', '');
const statusText = member.subscription_status || 'none';

// Format company type
const companyTypeLabels = {
brand: 'Brand',
publisher: 'Publisher',
agency: 'Agency',
adtech: 'Ad Tech',
other: 'Other'
};
const companyTypeText = member.is_personal
? '<span style="color: var(--color-text-muted);">Individual</span>'
: (companyTypeLabels[member.company_type] || '<span style="color: var(--color-text-muted);">-</span>');

// Format revenue tier
const revenueTierLabels = {
under_1m: '< $1M',
'1m_5m': '$1-5M',
'5m_50m': '$5-50M',
'50m_250m': '$50-250M',
'250m_1b': '$250M-1B',
'1b_plus': '$1B+'
};
const revenueTierText = member.is_personal
? '<span style="color: var(--color-text-muted);">-</span>'
: (revenueTierLabels[member.revenue_tier] || '<span style="color: var(--color-text-muted);">-</span>');

// Format subscription amount
let amountText = '-';
if (member.subscription_amount) {
Expand All @@ -543,10 +569,6 @@ <h4>This action cannot be undone</h4>
}
}

const agreementText = member.agreement_signed_at
? new Date(member.agreement_signed_at).toLocaleDateString()
: 'Not signed';

// Stripe customer link
const stripeLink = member.stripe_customer_id
? `https://dashboard.stripe.com/test/customers/${member.stripe_customer_id}`
Expand All @@ -555,11 +577,12 @@ <h4>This action cannot be undone</h4>
return `
<tr id="row-${member.company_id}">
<td><strong>${member.company_name || 'Unknown'}</strong></td>
<td>${companyTypeText}</td>
<td>${revenueTierText}</td>
<td>${member.owner_email || 'No email'}</td>
<td><span class="status-badge ${statusClass}">${statusText}</span></td>
<td><strong>${amountText}</strong></td>
<td class="${isExpiringSoon ? 'expiring-soon' : ''}">${periodEndText}</td>
<td>${agreementText}</td>
<td style="display: flex; gap: 4px; align-items: flex-start; flex-wrap: wrap;">
${stripeLink ? `<a href="${stripeLink}" target="_blank" class="btn-icon" title="View in Stripe">💳</a>` : ''}
<button class="btn-icon" onclick="viewPayments('${member.company_id}')" title="Payment History">📊</button>
Expand All @@ -577,22 +600,47 @@ <h4>This action cannot be undone</h4>
}

function exportToCSV() {
const headers = ['Company Name', 'Owner Email', 'Subscription Status', 'Current Period End', 'Agreement Signed', 'Created'];
const headers = ['Company Name', 'Company Type', 'Revenue Tier', 'Owner Email', 'Subscription Status', 'Amount', 'Current Period End', 'Created'];
const rows = filteredMembers.map(member => {
const periodEnd = member.subscription_current_period_end
? new Date(member.subscription_current_period_end * 1000).toLocaleDateString()
: '';
const agreementSigned = member.agreement_signed_at
? new Date(member.agreement_signed_at).toLocaleDateString()
: '';
const created = new Date(member.created_at).toLocaleDateString();

// Format company type
const companyTypeLabels = {
brand: 'Brand',
publisher: 'Publisher',
agency: 'Agency',
adtech: 'Ad Tech',
other: 'Other'
};
const companyType = member.is_personal ? 'Individual' : (companyTypeLabels[member.company_type] || '');

// Format revenue tier
const revenueTierLabels = {
under_1m: '< $1M',
'1m_5m': '$1-5M',
'5m_50m': '$5-50M',
'50m_250m': '$50-250M',
'250m_1b': '$250M-1B',
'1b_plus': '$1B+'
};
const revenueTier = member.is_personal ? '' : (revenueTierLabels[member.revenue_tier] || '');

// Format amount
const amount = member.subscription_amount
? `$${(member.subscription_amount / 100).toFixed(2)}/${member.subscription_interval || ''}`
: '';

return [
member.company_name,
companyType,
revenueTier,
member.owner_email,
member.subscription_status || 'none',
amount,
periodEnd,
agreementSigned,
created
];
});
Expand Down
130 changes: 129 additions & 1 deletion server/public/dashboard.html
Original file line number Diff line number Diff line change
Expand Up @@ -525,6 +525,16 @@ <h2 style="display: flex; align-items: center; gap: 8px; font-size: 16px;">
</div>
</div>

<!-- Organization Settings Card - only shown for non-personal workspaces -->
<div id="orgSettingsCard" class="card" style="display: none;">
<h2 style="display: flex; align-items: center; gap: 8px; font-size: 16px;">
<span style="font-size: 18px;">⚙️</span> Organization Settings
</h2>
<div id="orgSettingsDetails" style="font-size: 13px;">
<p style="color: var(--color-text-secondary);">Loading...</p>
</div>
</div>

<!-- Agreements (compact) - only shown for subscribers -->
<div id="agreementsCard" class="card" style="background: var(--color-bg-subtle); display: none;">
<h2 style="display: flex; align-items: center; gap: 8px; font-size: 14px; color: var(--color-text-secondary); margin-bottom: 10px;">
Expand Down Expand Up @@ -819,6 +829,15 @@ <h2 style="display: flex; align-items: center; gap: 8px; font-size: 14px; color:
// Load working groups for the user
loadWorkingGroups();

// Render Organization Settings Card for non-personal workspaces (owners/admins only)
const orgSettingsCard = document.getElementById('orgSettingsCard');
if (!isPersonal && (org.role === 'owner' || org.role === 'admin')) {
orgSettingsCard.style.display = 'block';
renderOrgSettings(org);
} else {
orgSettingsCard.style.display = 'none';
}

// Show/hide membership section based on this org's subscription status
const membershipSection = document.getElementById('membershipSection');

Expand Down Expand Up @@ -1470,7 +1489,12 @@ <h3 style="margin: 0 0 8px 0; color: var(--color-text-heading);">Create Your Pro
const billingData = await billingResponse.json();
return {
...org,
billing: billingData.subscription,
billing: {
...billingData.subscription,
company_type: billingData.company_type,
revenue_tier: billingData.revenue_tier,
is_personal: billingData.is_personal,
},
stripeCustomerId: billingData.stripe_customer_id,
customerSessionSecret: billingData.customer_session_secret,
billingError: null
Expand Down Expand Up @@ -2050,6 +2074,110 @@ <h3 style="margin: 0 0 8px 0; color: var(--color-text-heading);">Create Your Pro
}
}

// Organization settings helper labels
const companyTypeLabels = {
'brand': 'Brand / Marketer',
'publisher': 'Publisher',
'agency': 'Agency',
'adtech': 'Ad Tech Provider',
'other': 'Other'
};

const revenueTierLabels = {
'under_1m': '< $1M',
'1m_5m': '$1-5M',
'5m_50m': '$5-50M',
'50m_250m': '$50-250M',
'250m_1b': '$250M-1B',
'1b_plus': '$1B+'
};

// Render organization settings form
function renderOrgSettings(org) {
const container = document.getElementById('orgSettingsDetails');
if (!container) return;

// Get current values from billing data (which includes company_type, revenue_tier)
const companyType = org.billing?.company_type || null;
const revenueTier = org.billing?.revenue_tier || null;

container.innerHTML = `
<div style="display: flex; flex-direction: column; gap: 16px;">
<div>
<label style="display: block; font-weight: 500; margin-bottom: 6px; color: var(--color-text-heading);">Company Type</label>
<select id="orgSettingsCompanyType" style="width: 100%; padding: 8px 10px; border: 1px solid var(--color-border); border-radius: 6px; font-size: 13px; background: var(--color-bg-card);">
<option value="">Not specified</option>
${Object.entries(companyTypeLabels).map(([value, label]) =>
`<option value="${value}" ${companyType === value ? 'selected' : ''}>${label}</option>`
).join('')}
</select>
</div>

<div>
<label style="display: block; font-weight: 500; margin-bottom: 6px; color: var(--color-text-heading);">Annual Revenue</label>
<select id="orgSettingsRevenueTier" style="width: 100%; padding: 8px 10px; border: 1px solid var(--color-border); border-radius: 6px; font-size: 13px; background: var(--color-bg-card);">
<option value="">Not specified</option>
${Object.entries(revenueTierLabels).map(([value, label]) =>
`<option value="${value}" ${revenueTier === value ? 'selected' : ''}>${label}</option>`
).join('')}
</select>
</div>

<div id="orgSettingsMessage" style="display: none; padding: 8px; border-radius: 4px; font-size: 12px;"></div>

<button onclick="saveOrgSettings('${org.id}')" class="btn btn-primary btn-sm" style="align-self: flex-start;">
Save Settings
</button>
</div>
`;
}

// Save organization settings
async function saveOrgSettings(orgId) {
const companyType = document.getElementById('orgSettingsCompanyType').value || null;
const revenueTier = document.getElementById('orgSettingsRevenueTier').value || null;
const messageEl = document.getElementById('orgSettingsMessage');

try {
const response = await fetch(`/api/organizations/${orgId}/settings`, {
method: 'PATCH',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
company_type: companyType,
revenue_tier: revenueTier
}),
credentials: 'same-origin'
});

if (!response.ok) {
const data = await response.json();
throw new Error(data.message || 'Failed to save settings');
}

// Update the local org data
if (currentOrg && currentOrg.billing) {
currentOrg.billing.company_type = companyType;
currentOrg.billing.revenue_tier = revenueTier;
}

// Show success message
messageEl.style.display = 'block';
messageEl.style.background = 'var(--color-success-50)';
messageEl.style.color = 'var(--color-success-700)';
messageEl.textContent = 'Settings saved successfully';

setTimeout(() => {
messageEl.style.display = 'none';
}, 3000);
} catch (error) {
console.error('Error saving org settings:', error);
messageEl.style.display = 'block';
messageEl.style.background = 'var(--color-error-50)';
messageEl.style.color = 'var(--color-error-700)';
messageEl.textContent = error.message;
}
}

loadUserData();

// Handle browser back/forward cache (bfcache) restoration
Expand Down
4 changes: 2 additions & 2 deletions server/public/join-cta.js
Original file line number Diff line number Diff line change
Expand Up @@ -243,7 +243,7 @@ function renderJoinCta(options = {}) {
</ul>

<div class="join-cta-button-wrapper">
<a href="/auth/login?redirect=/dashboard" class="btn btn-primary">Join as a Company</a>
<a href="/auth/signup?return_to=/onboarding?signup=true" class="btn btn-primary">Join as a Company</a>
</div>
<p class="join-cta-card-footer">Open to brands, publishers, agencies, and ad tech providers</p>
</div>
Expand All @@ -268,7 +268,7 @@ function renderJoinCta(options = {}) {
</ul>

<div class="join-cta-button-wrapper">
<a href="/auth/login?redirect=/dashboard" class="btn btn-primary">Join as an Individual</a>
<a href="/auth/signup?return_to=/onboarding?signup=true" class="btn btn-primary">Join as an Individual</a>
</div>
<p class="join-cta-card-footer">For consultants, professionals, and enthusiasts</p>
</div>
Expand Down
2 changes: 1 addition & 1 deletion server/public/nav.js
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@
// Not logged in - show login/signup (links to AAO)
authSection = `
<a href="${authBaseUrl}/auth/login" class="navbar__link">Log in</a>
<a href="${authBaseUrl}/auth/login" class="navbar__btn navbar__btn--primary">Sign up</a>
<a href="${authBaseUrl}/auth/signup?return_to=/onboarding?signup=true" class="navbar__btn navbar__btn--primary">Sign up</a>
`;
}
}
Expand Down
Loading