ultimate-jekyll-manager
Version:
Ultimate Jekyll dependency manager
400 lines (338 loc) • 11.7 kB
JavaScript
// Libraries
import * as profileSection from './sections/profile.js';
import * as notificationsSection from './sections/notifications.js';
import * as securitySection from './sections/security.js';
import * as billingSection from './sections/billing.js';
import * as teamSection from './sections/team.js';
import * as referralsSection from './sections/referrals.js';
import * as apiKeysSection from './sections/api-keys.js';
import * as deleteSection from './sections/delete.js';
import * as dataRequestSection from './sections/data-request.js';
import * as connectionsSection from './sections/connections.js';
import * as refundSection from './sections/refund.js';
import webManager from 'web-manager';
import { getPaymentConfig } from '__main_assets__/js/libs/payment-config.js';
// Module
export default () => {
return new Promise(async function (resolve) {
// Initialize when DOM is ready
await webManager.dom().ready();
try {
await initializeAccount();
} catch (error) {
webManager.sentry().captureException(new Error('Failed to initialize account page', { cause: error }));
}
// Resolve after initialization
return resolve();
});
};
// Global state
let $navLinks = null;
let $sections = null;
// Config from _config.yml (available instantly, no fetch needed)
const paymentConfig = getPaymentConfig();
// Section modules map
const sectionModules = {
profile: profileSection,
notifications: notificationsSection,
security: securitySection,
billing: billingSection,
team: teamSection,
referrals: referralsSection,
'api-keys': apiKeysSection,
delete: deleteSection,
'data-request': dataRequestSection,
connections: connectionsSection,
refund: refundSection,
};
// Main initialization
async function initializeAccount() {
// Get DOM elements
$navLinks = document.querySelectorAll('#account-nav .nav-link');
$sections = document.querySelectorAll('.account-section');
// Initially hide all sections except loading
hideAllSectionsExceptLoading();
// Setup navigation
setupNavigation();
// Check if delete/data-request hash is present on initial load
if (window.location.hash === '#delete') {
showDeleteOption();
}
if (window.location.hash === '#data-request') {
showDataRequestOption();
}
if (window.location.hash === '#refund') {
showRefundOption();
}
// Initialize all section modules
Object.values(sectionModules).forEach(module => {
if (module.init) {
module.init();
}
});
// Setup auth listener
webManager.auth().listen({}, async (state) => {
console.log('Auth state with account data:', state);
/* @dev-only:start */
{
// Check for test subscription parameter
const urlParams = new URLSearchParams(window.location.search);
const testSubscription = urlParams.get('_dev_subscription');
if (testSubscription && webManager.isDevelopment()) {
try {
console.log(`Loading test subscription: ${testSubscription}`);
const testModule = await import(`./test-subscriptions/${testSubscription}.js`);
// Merge test fields INTO the real subscription (preserves real product, payment, etc.)
if (state.account) {
const real = state.account.subscription || {};
const test = testModule.default;
const merged = deepMerge(real, test);
// Write back so both JS and WM bindings see the same data
state.account.subscription = merged;
console.log('Test subscription merged:', merged);
}
} catch (error) {
console.error(`Failed to load test subscription '${testSubscription}':`, error);
}
}
}
/* @dev-only:end */
loadAllSectionData(state);
// Hide loading section and show the appropriate section
hideLoadingSection();
// Handle initial hash or show default section
handleHashChange();
});
}
// Load data for all sections
function loadAllSectionData(authState) {
const { user, account } = authState;
// Load data for each section (passing brand data where needed)
if (sectionModules.profile.loadData) {
sectionModules.profile.loadData(account, user);
}
if (sectionModules.notifications.loadData) {
sectionModules.notifications.loadData(account);
}
if (sectionModules.security.loadData) {
sectionModules.security.loadData(account);
}
if (sectionModules.billing.loadData) {
sectionModules.billing.loadData(account, paymentConfig);
}
if (sectionModules.team && sectionModules.team.loadData) {
sectionModules.team.loadData(account);
}
if (sectionModules.referrals && sectionModules.referrals.loadData) {
sectionModules.referrals.loadData(account);
}
if (sectionModules['api-keys'] && sectionModules['api-keys'].loadData) {
sectionModules['api-keys'].loadData(account);
}
if (sectionModules.delete.loadData) {
sectionModules.delete.loadData(account);
}
if (sectionModules['data-request'] && sectionModules['data-request'].loadData) {
sectionModules['data-request'].loadData(account);
}
if (sectionModules.connections.loadData) {
sectionModules.connections.loadData(account, webManager.config?.oauth2 || {});
}
if (sectionModules.refund.loadData) {
sectionModules.refund.loadData(account);
}
}
// Setup navigation between sections
function setupNavigation() {
// Add click listeners to nav links
$navLinks.forEach(link => {
link.addEventListener('click', (e) => {
e.preventDefault();
const sectionId = link.dataset.section;
showSection(sectionId);
// Update URL hash
window.location.hash = sectionId;
});
});
// Setup mobile dropdown navigation
const $mobileNavSelect = document.getElementById('mobile-nav-select');
if ($mobileNavSelect) {
$mobileNavSelect.addEventListener('change', (e) => {
const sectionId = e.target.value;
showSection(sectionId);
window.location.hash = sectionId;
});
}
// Listen for hash changes
window.addEventListener('hashchange', handleHashChange);
}
// Handle hash changes
function handleHashChange() {
const hash = window.location.hash.slice(1);
// Show hidden nav items based on hash
if (hash === 'delete') {
showDeleteOption();
}
if (hash === 'data-request') {
showDataRequestOption();
}
if (hash === 'refund') {
showRefundOption();
}
if (hash) {
// Check if the section exists
const $targetSection = document.getElementById(`${hash}-section`);
if ($targetSection) {
showSection(hash);
} else {
// Section doesn't exist, default to profile
console.warn(`Section "${hash}" not found, defaulting to profile`);
webManager.sentry().captureException(new Error(`Invalid account section hash: ${hash}`));
window.location.hash = '#profile';
showSection('profile');
}
} else {
// No hash, show profile by default
showSection('profile');
}
}
// Show delete option in navigation
function showDeleteOption() {
// Show desktop nav item
const $deleteNavItem = document.getElementById('delete-nav-item');
if ($deleteNavItem) {
$deleteNavItem.classList.remove('d-none');
}
// Add mobile dropdown option if not exists
const $mobileNavSelect = document.getElementById('mobile-nav-select');
if ($mobileNavSelect) {
const deleteOption = $mobileNavSelect.querySelector('option[value="delete"]');
if (!deleteOption) {
const option = document.createElement('option');
option.value = 'delete';
option.textContent = 'Delete Account';
option.className = 'text-danger';
$mobileNavSelect.appendChild(option);
}
}
}
// Show data request option in navigation
function showDataRequestOption() {
// Show desktop nav item
const $dataRequestNavItem = document.getElementById('data-request-nav-item');
if ($dataRequestNavItem) {
$dataRequestNavItem.classList.remove('d-none');
}
// Add mobile dropdown option if not exists
const $mobileNavSelect = document.getElementById('mobile-nav-select');
if ($mobileNavSelect) {
const dataRequestOption = $mobileNavSelect.querySelector('option[value="data-request"]');
if (!dataRequestOption) {
const option = document.createElement('option');
option.value = 'data-request';
option.textContent = 'Data Request';
$mobileNavSelect.appendChild(option);
}
}
}
// Show refund option in navigation
function showRefundOption() {
// Show desktop nav item
const $refundNavItem = document.getElementById('refund-nav-item');
if ($refundNavItem) {
$refundNavItem.classList.remove('d-none');
}
// Add mobile dropdown option if not exists
const $mobileNavSelect = document.getElementById('mobile-nav-select');
if ($mobileNavSelect) {
const refundOption = $mobileNavSelect.querySelector('option[value="refund"]');
if (!refundOption) {
const option = document.createElement('option');
option.value = 'refund';
option.textContent = 'Refund';
$mobileNavSelect.appendChild(option);
}
}
}
// Hide all sections except loading
function hideAllSectionsExceptLoading() {
$sections.forEach(section => {
if (section.id !== 'loading-section') {
section.classList.add('d-none');
}
});
}
// Hide loading section
function hideLoadingSection() {
const $loadingSection = document.getElementById('loading-section');
if ($loadingSection) {
$loadingSection.classList.add('d-none');
}
}
// Show specific section
function showSection(sectionId) {
// Don't show sections while loading
const $loadingSection = document.getElementById('loading-section');
if ($loadingSection && !$loadingSection.classList.contains('d-none')) {
// Still loading, don't switch sections yet
return;
}
// Hide all sections
$sections.forEach(section => {
section.classList.add('d-none');
});
// Show selected section
const $targetSection = document.getElementById(`${sectionId}-section`);
if ($targetSection) {
$targetSection.classList.remove('d-none');
// Track section view when actually shown
trackAccountSectionView(sectionId);
}
// Update nav active state
$navLinks.forEach(link => {
if (link.dataset.section === sectionId) {
link.classList.add('active');
} else {
link.classList.remove('active');
}
});
// Update mobile dropdown to match
const $mobileNavSelect = document.getElementById('mobile-nav-select');
if ($mobileNavSelect) {
$mobileNavSelect.value = sectionId;
}
// Call section's onShow method if it exists
const sectionModule = sectionModules[sectionId];
if (sectionModule && sectionModule.onShow) {
sectionModule.onShow();
}
}
// Deep merge utility (target fields are overwritten by source fields)
function deepMerge(target, source) {
const result = { ...target };
Object.keys(source).forEach(key => {
const sourceVal = source[key];
const targetVal = target[key];
if (sourceVal && typeof sourceVal === 'object' && !Array.isArray(sourceVal)
&& targetVal && typeof targetVal === 'object' && !Array.isArray(targetVal)) {
result[key] = deepMerge(targetVal, sourceVal);
} else {
result[key] = sourceVal;
}
});
return result;
}
// Tracking functions
function trackAccountSectionView(sectionId) {
gtag('event', 'account_section_view', {
section_name: sectionId
});
fbq('trackCustom', 'AccountSectionView', {
section: sectionId
});
ttq.track('ViewContent', {
content_id: `account-${sectionId}`,
content_type: 'product',
content_name: `Account ${sectionId}`
});
}