Files
orion/static/shared/js/feature-store.js
Samir Boulahtit 94677f946b fix: resolve architecture validation warnings
- Replace window.apiClient with apiClient in feature-store.js (JS-002)
- Replace window.apiClient with apiClient in upgrade-prompts.js (JS-002)
- Replace inline SVGs with $icon() helper in features.html (FE-002)
- Add check-circle-filled icon to icons.js

Architecture validation now passes with 0 errors, 0 warnings.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-31 18:59:41 +01:00

206 lines
6.5 KiB
JavaScript

// static/shared/js/feature-store.js
/**
* Feature Store for Alpine.js
*
* Provides feature availability checking for tier-based access control.
* Loads features from the API on init and caches them for the session.
*
* Usage in templates:
*
* 1. Check if feature is available:
* <div x-show="$store.features.has('analytics_dashboard')">
* Analytics content here
* </div>
*
* 2. Show upgrade prompt if not available:
* <div x-show="!$store.features.has('analytics_dashboard')">
* <p>Upgrade to access Analytics</p>
* </div>
*
* 3. Conditionally render with x-if:
* <template x-if="$store.features.has('api_access')">
* <a href="/settings/api">API Settings</a>
* </template>
*
* 4. Use feature data for upgrade prompts:
* <p x-text="$store.features.getUpgradeTier('analytics_dashboard')"></p>
*
* 5. Get current tier info:
* <span x-text="$store.features.tierName"></span>
*/
(function () {
'use strict';
// Use centralized logger if available
const log = window.LogConfig?.log || console;
/**
* Feature Store
*/
const featureStore = {
// State
features: [], // Array of feature codes available to vendor
featuresMap: {}, // Full feature info keyed by code
tierCode: null, // Current tier code
tierName: null, // Current tier name
loading: true, // Loading state
loaded: false, // Whether features have been loaded
error: null, // Error message if load failed
/**
* Initialize the feature store
* Called automatically when Alpine starts
*/
async init() {
log.debug('[FeatureStore] Initializing...');
await this.loadFeatures();
},
/**
* Load features from API
*/
async loadFeatures() {
// Don't reload if already loaded
if (this.loaded) {
log.debug('[FeatureStore] Already loaded, skipping');
return;
}
// Get vendor code from URL
const vendorCode = this.getVendorCode();
if (!vendorCode) {
log.warn('[FeatureStore] No vendor code found in URL');
this.loading = false;
return;
}
try {
this.loading = true;
this.error = null;
// Fetch available features (lightweight endpoint)
const response = await apiClient.get('/vendor/features/available');
this.features = response.features || [];
this.tierCode = response.tier_code;
this.tierName = response.tier_name;
this.loaded = true;
log.debug(`[FeatureStore] Loaded ${this.features.length} features for ${this.tierName} tier`);
} catch (error) {
log.error('[FeatureStore] Failed to load features:', error);
this.error = error.message || 'Failed to load features';
// Set empty array so checks don't fail
this.features = [];
} finally {
this.loading = false;
}
},
/**
* Load full feature details (with metadata)
* Use this when you need upgrade info
*/
async loadFullFeatures() {
const vendorCode = this.getVendorCode();
if (!vendorCode) return;
try {
const response = await apiClient.get('/vendor/features');
// Build map for quick lookup
this.featuresMap = {};
for (const feature of response.features) {
this.featuresMap[feature.code] = feature;
}
log.debug(`[FeatureStore] Loaded full details for ${response.features.length} features`);
} catch (error) {
log.error('[FeatureStore] Failed to load full features:', error);
}
},
/**
* Check if vendor has access to a feature
* @param {string} featureCode - The feature code to check
* @returns {boolean} - Whether the feature is available
*/
has(featureCode) {
return this.features.includes(featureCode);
},
/**
* Check if vendor has access to ANY of the given features
* @param {...string} featureCodes - Feature codes to check
* @returns {boolean} - Whether any feature is available
*/
hasAny(...featureCodes) {
return featureCodes.some(code => this.has(code));
},
/**
* Check if vendor has access to ALL of the given features
* @param {...string} featureCodes - Feature codes to check
* @returns {boolean} - Whether all features are available
*/
hasAll(...featureCodes) {
return featureCodes.every(code => this.has(code));
},
/**
* Get feature info (requires loadFullFeatures first)
* @param {string} featureCode - The feature code
* @returns {object|null} - Feature info or null
*/
getFeature(featureCode) {
return this.featuresMap[featureCode] || null;
},
/**
* Get the tier name required for a feature
* @param {string} featureCode - The feature code
* @returns {string|null} - Tier name or null
*/
getUpgradeTier(featureCode) {
const feature = this.getFeature(featureCode);
return feature?.minimum_tier_name || null;
},
/**
* Get vendor code from URL
* @returns {string|null}
*/
getVendorCode() {
const path = window.location.pathname;
const segments = path.split('/').filter(Boolean);
if (segments[0] === 'vendor' && segments[1]) {
return segments[1];
}
return null;
},
/**
* Reload features (e.g., after tier change)
*/
async reload() {
this.loaded = false;
this.features = [];
this.featuresMap = {};
await this.loadFeatures();
}
};
// Register Alpine store when Alpine is available
document.addEventListener('alpine:init', () => {
Alpine.store('features', featureStore);
log.debug('[FeatureStore] Registered as Alpine store');
});
// Also expose globally for non-Alpine usage
window.FeatureStore = featureStore;
})();