- Remove redundant 1/4 progress counter from header - Make step indicators mobile-friendly (smaller circles, hidden labels) - Add CSV URL help text pointing to Letzshop Admin > API > Export Products - Fix AttributeError in order sync progress (use correct model attributes) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
613 lines
24 KiB
JavaScript
613 lines
24 KiB
JavaScript
// static/vendor/js/onboarding.js
|
|
// noqa: js-003 - Standalone page without vendor layout (no base.html extends)
|
|
// noqa: js-004 - Standalone page has no currentPage sidebar highlight
|
|
/**
|
|
* Vendor Onboarding Wizard
|
|
*
|
|
* Handles the 4-step mandatory onboarding flow:
|
|
* 1. Company Profile Setup
|
|
* 2. Letzshop API Configuration
|
|
* 3. Product & Order Import Configuration
|
|
* 4. Order Sync (historical import)
|
|
*/
|
|
|
|
const onboardingLog = window.LogConfig?.createLogger('ONBOARDING') || console;
|
|
|
|
// Onboarding translations
|
|
const onboardingTranslations = {
|
|
en: {
|
|
title: 'Welcome to Wizamart',
|
|
subtitle: 'Complete these steps to set up your store',
|
|
steps: {
|
|
company_profile: 'Company Profile',
|
|
letzshop_api: 'Letzshop API',
|
|
product_import: 'Product Import',
|
|
order_sync: 'Order Sync',
|
|
},
|
|
step1: {
|
|
title: 'Company Profile Setup',
|
|
description: 'Tell us about your business. This information will be used for invoices and your store profile.',
|
|
company_name: 'Company Name',
|
|
brand_name: 'Brand Name',
|
|
brand_name_help: 'The name customers will see',
|
|
description_label: 'Description',
|
|
description_placeholder: 'Brief description of your business',
|
|
contact_email: 'Contact Email',
|
|
contact_phone: 'Contact Phone',
|
|
website: 'Website',
|
|
business_address: 'Business Address',
|
|
tax_number: 'Tax Number (VAT)',
|
|
tax_number_placeholder: 'e.g., LU12345678',
|
|
default_language: 'Default Shop Language',
|
|
dashboard_language: 'Dashboard Language',
|
|
},
|
|
step2: {
|
|
title: 'Letzshop API Configuration',
|
|
description: 'Connect your Letzshop marketplace account to sync orders automatically.',
|
|
api_key: 'Letzshop API Key',
|
|
api_key_placeholder: 'Enter your API key',
|
|
api_key_help: 'Get your API key from Letzshop Support team',
|
|
shop_slug: 'Shop Slug',
|
|
shop_slug_help: 'Enter the last part of your Letzshop vendor URL',
|
|
test_connection: 'Test Connection',
|
|
testing: 'Testing...',
|
|
connection_success: 'Connection successful',
|
|
connection_failed: 'Connection failed',
|
|
},
|
|
step3: {
|
|
title: 'Product Import Configuration',
|
|
description: 'Configure how products are imported from your CSV feeds.',
|
|
csv_urls: 'CSV Feed URLs',
|
|
csv_url_fr: 'French CSV URL',
|
|
csv_url_en: 'English CSV URL',
|
|
csv_url_de: 'German CSV URL',
|
|
csv_url_help: 'Find your CSV URL in Letzshop Admin Panel > API > Export Products',
|
|
default_tax_rate: 'Default Tax Rate (%)',
|
|
delivery_method: 'Delivery Method',
|
|
delivery_package: 'Package Delivery',
|
|
delivery_pickup: 'Store Pickup',
|
|
preorder_days: 'Preorder Days',
|
|
preorder_days_help: 'Days before product is available after order',
|
|
},
|
|
step4: {
|
|
title: 'Historical Order Import',
|
|
description: 'Import your existing orders from Letzshop to start managing them in Wizamart.',
|
|
days_back: 'Import orders from last',
|
|
days: 'days',
|
|
start_import: 'Start Import',
|
|
importing: 'Importing...',
|
|
import_complete: 'Import Complete!',
|
|
orders_imported: 'orders imported',
|
|
skip_step: 'Skip this step',
|
|
},
|
|
buttons: {
|
|
save_continue: 'Save & Continue',
|
|
saving: 'Saving...',
|
|
back: 'Back',
|
|
complete: 'Complete Setup',
|
|
retry: 'Retry',
|
|
},
|
|
loading: 'Loading your setup...',
|
|
errors: {
|
|
load_failed: 'Failed to load onboarding status',
|
|
save_failed: 'Failed to save. Please try again.',
|
|
},
|
|
},
|
|
fr: {
|
|
title: 'Bienvenue sur Wizamart',
|
|
subtitle: 'Complétez ces étapes pour configurer votre boutique',
|
|
steps: {
|
|
company_profile: 'Profil Entreprise',
|
|
letzshop_api: 'API Letzshop',
|
|
product_import: 'Import Produits',
|
|
order_sync: 'Sync Commandes',
|
|
},
|
|
step1: {
|
|
title: 'Configuration du Profil Entreprise',
|
|
description: 'Parlez-nous de votre entreprise. Ces informations seront utilisées pour les factures et le profil de votre boutique.',
|
|
company_name: 'Nom de l\'Entreprise',
|
|
brand_name: 'Nom de la Marque',
|
|
brand_name_help: 'Le nom que les clients verront',
|
|
description_label: 'Description',
|
|
description_placeholder: 'Brève description de votre activité',
|
|
contact_email: 'Email de Contact',
|
|
contact_phone: 'Téléphone de Contact',
|
|
website: 'Site Web',
|
|
business_address: 'Adresse Professionnelle',
|
|
tax_number: 'Numéro de TVA',
|
|
tax_number_placeholder: 'ex: LU12345678',
|
|
default_language: 'Langue par Défaut de la Boutique',
|
|
dashboard_language: 'Langue du Tableau de Bord',
|
|
},
|
|
step2: {
|
|
title: 'Configuration de l\'API Letzshop',
|
|
description: 'Connectez votre compte Letzshop pour synchroniser automatiquement les commandes.',
|
|
api_key: 'Clé API Letzshop',
|
|
api_key_placeholder: 'Entrez votre clé API',
|
|
api_key_help: 'Obtenez votre clé API auprès de l\'équipe Support Letzshop',
|
|
shop_slug: 'Identifiant Boutique',
|
|
shop_slug_help: 'Entrez la dernière partie de votre URL vendeur Letzshop',
|
|
test_connection: 'Tester la Connexion',
|
|
testing: 'Test en cours...',
|
|
connection_success: 'Connexion réussie',
|
|
connection_failed: 'Échec de la connexion',
|
|
},
|
|
step3: {
|
|
title: 'Configuration Import Produits',
|
|
description: 'Configurez comment les produits sont importés depuis vos flux CSV.',
|
|
csv_urls: 'URLs des Flux CSV',
|
|
csv_url_fr: 'URL CSV Français',
|
|
csv_url_en: 'URL CSV Anglais',
|
|
csv_url_de: 'URL CSV Allemand',
|
|
csv_url_help: 'Trouvez votre URL CSV dans Letzshop Admin > API > Exporter Produits',
|
|
default_tax_rate: 'Taux de TVA par Défaut (%)',
|
|
delivery_method: 'Méthode de Livraison',
|
|
delivery_package: 'Livraison Colis',
|
|
delivery_pickup: 'Retrait en Magasin',
|
|
preorder_days: 'Jours de Précommande',
|
|
preorder_days_help: 'Jours avant disponibilité du produit après commande',
|
|
},
|
|
step4: {
|
|
title: 'Import Historique des Commandes',
|
|
description: 'Importez vos commandes existantes de Letzshop pour commencer à les gérer dans Wizamart.',
|
|
days_back: 'Importer les commandes des derniers',
|
|
days: 'jours',
|
|
start_import: 'Démarrer l\'Import',
|
|
importing: 'Import en cours...',
|
|
import_complete: 'Import Terminé !',
|
|
orders_imported: 'commandes importées',
|
|
skip_step: 'Passer cette étape',
|
|
},
|
|
buttons: {
|
|
save_continue: 'Enregistrer & Continuer',
|
|
saving: 'Enregistrement...',
|
|
back: 'Retour',
|
|
complete: 'Terminer la Configuration',
|
|
retry: 'Réessayer',
|
|
},
|
|
loading: 'Chargement de votre configuration...',
|
|
errors: {
|
|
load_failed: 'Échec du chargement du statut d\'onboarding',
|
|
save_failed: 'Échec de l\'enregistrement. Veuillez réessayer.',
|
|
},
|
|
},
|
|
de: {
|
|
title: 'Willkommen bei Wizamart',
|
|
subtitle: 'Führen Sie diese Schritte aus, um Ihren Shop einzurichten',
|
|
steps: {
|
|
company_profile: 'Firmenprofil',
|
|
letzshop_api: 'Letzshop API',
|
|
product_import: 'Produktimport',
|
|
order_sync: 'Bestellsync',
|
|
},
|
|
step1: {
|
|
title: 'Firmenprofil Einrichten',
|
|
description: 'Erzählen Sie uns von Ihrem Unternehmen. Diese Informationen werden für Rechnungen und Ihr Shop-Profil verwendet.',
|
|
company_name: 'Firmenname',
|
|
brand_name: 'Markenname',
|
|
brand_name_help: 'Der Name, den Kunden sehen werden',
|
|
description_label: 'Beschreibung',
|
|
description_placeholder: 'Kurze Beschreibung Ihres Unternehmens',
|
|
contact_email: 'Kontakt-E-Mail',
|
|
contact_phone: 'Kontakttelefon',
|
|
website: 'Website',
|
|
business_address: 'Geschäftsadresse',
|
|
tax_number: 'Steuernummer (USt-IdNr.)',
|
|
tax_number_placeholder: 'z.B. LU12345678',
|
|
default_language: 'Standard-Shop-Sprache',
|
|
dashboard_language: 'Dashboard-Sprache',
|
|
},
|
|
step2: {
|
|
title: 'Letzshop API Konfiguration',
|
|
description: 'Verbinden Sie Ihr Letzshop-Konto, um Bestellungen automatisch zu synchronisieren.',
|
|
api_key: 'Letzshop API-Schlüssel',
|
|
api_key_placeholder: 'Geben Sie Ihren API-Schlüssel ein',
|
|
api_key_help: 'Erhalten Sie Ihren API-Schlüssel vom Letzshop Support-Team',
|
|
shop_slug: 'Shop-Slug',
|
|
shop_slug_help: 'Geben Sie den letzten Teil Ihrer Letzshop-Verkäufer-URL ein',
|
|
test_connection: 'Verbindung Testen',
|
|
testing: 'Teste...',
|
|
connection_success: 'Verbindung erfolgreich',
|
|
connection_failed: 'Verbindung fehlgeschlagen',
|
|
},
|
|
step3: {
|
|
title: 'Produktimport Konfiguration',
|
|
description: 'Konfigurieren Sie, wie Produkte aus Ihren CSV-Feeds importiert werden.',
|
|
csv_urls: 'CSV-Feed-URLs',
|
|
csv_url_fr: 'Französische CSV-URL',
|
|
csv_url_en: 'Englische CSV-URL',
|
|
csv_url_de: 'Deutsche CSV-URL',
|
|
csv_url_help: 'Finden Sie Ihre CSV-URL im Letzshop Admin-Panel > API > Produkte exportieren',
|
|
default_tax_rate: 'Standard-Steuersatz (%)',
|
|
delivery_method: 'Liefermethode',
|
|
delivery_package: 'Paketlieferung',
|
|
delivery_pickup: 'Abholung im Geschäft',
|
|
preorder_days: 'Vorbestelltage',
|
|
preorder_days_help: 'Tage bis zur Verfügbarkeit nach Bestellung',
|
|
},
|
|
step4: {
|
|
title: 'Historischer Bestellimport',
|
|
description: 'Importieren Sie Ihre bestehenden Bestellungen von Letzshop, um sie in Wizamart zu verwalten.',
|
|
days_back: 'Bestellungen der letzten importieren',
|
|
days: 'Tage',
|
|
start_import: 'Import Starten',
|
|
importing: 'Importiere...',
|
|
import_complete: 'Import Abgeschlossen!',
|
|
orders_imported: 'Bestellungen importiert',
|
|
skip_step: 'Diesen Schritt überspringen',
|
|
},
|
|
buttons: {
|
|
save_continue: 'Speichern & Fortfahren',
|
|
saving: 'Speichern...',
|
|
back: 'Zurück',
|
|
complete: 'Einrichtung Abschließen',
|
|
retry: 'Erneut versuchen',
|
|
},
|
|
loading: 'Ihre Einrichtung wird geladen...',
|
|
errors: {
|
|
load_failed: 'Onboarding-Status konnte nicht geladen werden',
|
|
save_failed: 'Speichern fehlgeschlagen. Bitte versuchen Sie es erneut.',
|
|
},
|
|
},
|
|
};
|
|
|
|
function vendorOnboarding(initialLang = 'en') {
|
|
return {
|
|
// Language
|
|
lang: initialLang || localStorage.getItem('onboarding_lang') || 'en',
|
|
availableLanguages: ['en', 'fr', 'de'],
|
|
languageNames: { en: 'English', fr: 'Français', de: 'Deutsch' },
|
|
languageFlags: { en: '🇬🇧', fr: '🇫🇷', de: '🇩🇪' },
|
|
|
|
// Translation helper
|
|
t(key) {
|
|
const keys = key.split('.');
|
|
let value = onboardingTranslations[this.lang];
|
|
for (const k of keys) {
|
|
value = value?.[k];
|
|
}
|
|
return value || key;
|
|
},
|
|
|
|
// Change language
|
|
setLang(newLang) {
|
|
this.lang = newLang;
|
|
localStorage.setItem('onboarding_lang', newLang);
|
|
},
|
|
|
|
// State
|
|
loading: true,
|
|
saving: false,
|
|
testing: false,
|
|
error: null,
|
|
|
|
// Steps configuration (will be populated with translated titles)
|
|
get steps() {
|
|
return [
|
|
{ id: 'company_profile', title: this.t('steps.company_profile') },
|
|
{ id: 'letzshop_api', title: this.t('steps.letzshop_api') },
|
|
{ id: 'product_import', title: this.t('steps.product_import') },
|
|
{ id: 'order_sync', title: this.t('steps.order_sync') },
|
|
];
|
|
},
|
|
|
|
// Current state
|
|
currentStep: 'company_profile',
|
|
completedSteps: 0,
|
|
status: null,
|
|
|
|
// Form data
|
|
formData: {
|
|
// Step 1: Company Profile
|
|
company_name: '',
|
|
brand_name: '',
|
|
description: '',
|
|
contact_email: '',
|
|
contact_phone: '',
|
|
website: '',
|
|
business_address: '',
|
|
tax_number: '',
|
|
default_language: 'fr',
|
|
dashboard_language: 'fr',
|
|
|
|
// Step 2: Letzshop API
|
|
api_key: '',
|
|
shop_slug: '',
|
|
|
|
// Step 3: Product Import
|
|
csv_url_fr: '',
|
|
csv_url_en: '',
|
|
csv_url_de: '',
|
|
default_tax_rate: 17,
|
|
delivery_method: 'package_delivery',
|
|
preorder_days: 1,
|
|
|
|
// Step 4: Order Sync
|
|
days_back: 90,
|
|
},
|
|
|
|
// Letzshop connection test state
|
|
connectionStatus: null, // null, 'success', 'failed'
|
|
connectionError: null,
|
|
|
|
// Order sync state
|
|
syncJobId: null,
|
|
syncProgress: 0,
|
|
syncPhase: '',
|
|
ordersImported: 0,
|
|
syncComplete: false,
|
|
syncPollInterval: null,
|
|
|
|
// Computed
|
|
get currentStepIndex() {
|
|
return this.steps.findIndex(s => s.id === this.currentStep);
|
|
},
|
|
|
|
// Initialize
|
|
async init() {
|
|
await this.loadStatus();
|
|
},
|
|
|
|
// Load current onboarding status
|
|
async loadStatus() {
|
|
this.loading = true;
|
|
this.error = null;
|
|
|
|
try {
|
|
const response = await apiClient.get('/vendor/onboarding/status');
|
|
this.status = response;
|
|
this.currentStep = response.current_step;
|
|
this.completedSteps = response.completed_steps_count;
|
|
|
|
// Pre-populate form data from status if available
|
|
if (response.company_profile?.data) {
|
|
Object.assign(this.formData, response.company_profile.data);
|
|
}
|
|
|
|
// Check if we were in the middle of an order sync
|
|
if (response.order_sync?.job_id && this.currentStep === 'order_sync') {
|
|
this.syncJobId = response.order_sync.job_id;
|
|
this.startSyncPolling();
|
|
}
|
|
|
|
// Load step-specific data
|
|
await this.loadStepData();
|
|
} catch (err) {
|
|
onboardingLog.error('Failed to load onboarding status:', err);
|
|
this.error = err.message || 'Failed to load onboarding status';
|
|
} finally {
|
|
this.loading = false;
|
|
}
|
|
},
|
|
|
|
// Load data for current step
|
|
async loadStepData() {
|
|
try {
|
|
if (this.currentStep === 'company_profile') {
|
|
const data = await apiClient.get('/vendor/onboarding/step/company-profile');
|
|
if (data) {
|
|
Object.assign(this.formData, data);
|
|
}
|
|
} else if (this.currentStep === 'product_import') {
|
|
const data = await apiClient.get('/vendor/onboarding/step/product-import');
|
|
if (data) {
|
|
Object.assign(this.formData, {
|
|
csv_url_fr: data.csv_url_fr || '',
|
|
csv_url_en: data.csv_url_en || '',
|
|
csv_url_de: data.csv_url_de || '',
|
|
default_tax_rate: data.default_tax_rate || 17,
|
|
delivery_method: data.delivery_method || 'package_delivery',
|
|
preorder_days: data.preorder_days || 1,
|
|
});
|
|
}
|
|
}
|
|
} catch (err) {
|
|
onboardingLog.warn('Failed to load step data:', err);
|
|
}
|
|
},
|
|
|
|
// Check if a step is completed
|
|
isStepCompleted(stepId) {
|
|
if (!this.status) return false;
|
|
const stepData = this.status[stepId];
|
|
return stepData?.completed === true;
|
|
},
|
|
|
|
// Go to previous step
|
|
goToPreviousStep() {
|
|
const prevIndex = this.currentStepIndex - 1;
|
|
if (prevIndex >= 0) {
|
|
this.currentStep = this.steps[prevIndex].id;
|
|
this.loadStepData();
|
|
}
|
|
},
|
|
|
|
// Test Letzshop API connection
|
|
async testLetzshopApi() {
|
|
this.testing = true;
|
|
this.connectionStatus = null;
|
|
this.connectionError = null;
|
|
|
|
try {
|
|
const response = await apiClient.post('/vendor/onboarding/step/letzshop-api/test', {
|
|
api_key: this.formData.api_key,
|
|
shop_slug: this.formData.shop_slug,
|
|
});
|
|
|
|
if (response.success) {
|
|
this.connectionStatus = 'success';
|
|
} else {
|
|
this.connectionStatus = 'failed';
|
|
this.connectionError = response.message;
|
|
}
|
|
} catch (err) {
|
|
this.connectionStatus = 'failed';
|
|
this.connectionError = err.message || 'Connection test failed';
|
|
} finally {
|
|
this.testing = false;
|
|
}
|
|
},
|
|
|
|
// Start order sync
|
|
async startOrderSync() {
|
|
this.saving = true;
|
|
|
|
try {
|
|
const response = await apiClient.post('/vendor/onboarding/step/order-sync/trigger', {
|
|
days_back: parseInt(this.formData.days_back),
|
|
include_products: true,
|
|
});
|
|
|
|
if (response.success && response.job_id) {
|
|
this.syncJobId = response.job_id;
|
|
this.startSyncPolling();
|
|
} else {
|
|
throw new Error(response.message || 'Failed to start import');
|
|
}
|
|
} catch (err) {
|
|
onboardingLog.error('Failed to start order sync:', err);
|
|
this.error = err.message || 'Failed to start import';
|
|
} finally {
|
|
this.saving = false;
|
|
}
|
|
},
|
|
|
|
// Start polling for sync progress
|
|
startSyncPolling() {
|
|
this.syncPollInterval = setInterval(async () => {
|
|
await this.pollSyncProgress();
|
|
}, 2000);
|
|
},
|
|
|
|
// Poll sync progress
|
|
async pollSyncProgress() {
|
|
try {
|
|
const response = await apiClient.get(
|
|
`/vendor/onboarding/step/order-sync/progress/${this.syncJobId}`
|
|
);
|
|
|
|
this.syncProgress = response.progress_percentage || 0;
|
|
this.syncPhase = this.formatPhase(response.current_phase);
|
|
this.ordersImported = response.orders_imported || 0;
|
|
|
|
if (response.status === 'completed' || response.status === 'failed') {
|
|
this.stopSyncPolling();
|
|
this.syncComplete = true;
|
|
this.syncProgress = response.status === 'completed' ? 100 : this.syncProgress;
|
|
}
|
|
} catch (err) {
|
|
onboardingLog.error('Failed to poll sync progress:', err);
|
|
}
|
|
},
|
|
|
|
// Stop sync polling
|
|
stopSyncPolling() {
|
|
if (this.syncPollInterval) {
|
|
clearInterval(this.syncPollInterval);
|
|
this.syncPollInterval = null;
|
|
}
|
|
},
|
|
|
|
// Format phase for display
|
|
formatPhase(phase) {
|
|
const phases = {
|
|
fetching: 'Fetching orders from Letzshop...',
|
|
orders: 'Processing orders...',
|
|
products: 'Importing products...',
|
|
finalizing: 'Finalizing import...',
|
|
complete: 'Import complete!',
|
|
};
|
|
return phases[phase] || 'Processing...';
|
|
},
|
|
|
|
// Save current step and continue
|
|
async saveAndContinue() {
|
|
this.saving = true;
|
|
this.error = null;
|
|
|
|
try {
|
|
let endpoint = '';
|
|
let payload = {};
|
|
|
|
switch (this.currentStep) {
|
|
case 'company_profile':
|
|
endpoint = '/vendor/onboarding/step/company-profile';
|
|
payload = {
|
|
company_name: this.formData.company_name,
|
|
brand_name: this.formData.brand_name,
|
|
description: this.formData.description,
|
|
contact_email: this.formData.contact_email,
|
|
contact_phone: this.formData.contact_phone,
|
|
website: this.formData.website,
|
|
business_address: this.formData.business_address,
|
|
tax_number: this.formData.tax_number,
|
|
default_language: this.formData.default_language,
|
|
dashboard_language: this.formData.dashboard_language,
|
|
};
|
|
break;
|
|
|
|
case 'letzshop_api':
|
|
endpoint = '/vendor/onboarding/step/letzshop-api';
|
|
payload = {
|
|
api_key: this.formData.api_key,
|
|
shop_slug: this.formData.shop_slug,
|
|
};
|
|
break;
|
|
|
|
case 'product_import':
|
|
endpoint = '/vendor/onboarding/step/product-import';
|
|
payload = {
|
|
csv_url_fr: this.formData.csv_url_fr || null,
|
|
csv_url_en: this.formData.csv_url_en || null,
|
|
csv_url_de: this.formData.csv_url_de || null,
|
|
default_tax_rate: parseInt(this.formData.default_tax_rate),
|
|
delivery_method: this.formData.delivery_method,
|
|
preorder_days: parseInt(this.formData.preorder_days),
|
|
};
|
|
break;
|
|
|
|
case 'order_sync':
|
|
// Complete onboarding
|
|
endpoint = '/vendor/onboarding/step/order-sync/complete';
|
|
payload = {
|
|
job_id: this.syncJobId,
|
|
};
|
|
break;
|
|
}
|
|
|
|
const response = await apiClient.post(endpoint, payload);
|
|
|
|
if (!response.success) {
|
|
throw new Error(response.message || 'Save failed');
|
|
}
|
|
|
|
// Handle completion
|
|
if (response.onboarding_completed || response.redirect_url) {
|
|
// Redirect to dashboard
|
|
window.location.href = response.redirect_url || window.location.pathname.replace('/onboarding', '/dashboard');
|
|
return;
|
|
}
|
|
|
|
// Move to next step
|
|
if (response.next_step) {
|
|
this.currentStep = response.next_step;
|
|
this.completedSteps++;
|
|
await this.loadStepData();
|
|
}
|
|
|
|
} catch (err) {
|
|
onboardingLog.error('Failed to save step:', err);
|
|
this.error = err.message || 'Failed to save. Please try again.';
|
|
} finally {
|
|
this.saving = false;
|
|
}
|
|
},
|
|
|
|
// Dark mode
|
|
get dark() {
|
|
return localStorage.getItem('dark') === 'true' ||
|
|
(!localStorage.getItem('dark') && window.matchMedia('(prefers-color-scheme: dark)').matches);
|
|
},
|
|
};
|
|
}
|