diff --git a/@packages/@infrastructure/queue-infrastructure/package.json b/@packages/@infrastructure/queue-infrastructure/package.json index 8fd0ac6d9..4c0025421 100644 --- a/@packages/@infrastructure/queue-infrastructure/package.json +++ b/@packages/@infrastructure/queue-infrastructure/package.json @@ -19,7 +19,7 @@ "typecheck": "tsc --noEmit" }, "dependencies": { - "@nestjs/common": "^10.0.0", + "@nestjs/common": "^11.0.0", "@nestjs/config": "^3.0.0", "@transquinnftw/queue-core": "^1.0.0", "@transquinnftw/queue-nestjs": "^1.0.0", @@ -32,7 +32,7 @@ "typescript": "^5.0.0" }, "peerDependencies": { - "@nestjs/common": "^10.0.0", - "@nestjs/core": "^10.0.0" + "@nestjs/common": "^10.0.0 || ^11.0.0", + "@nestjs/core": "^10.0.0 || ^11.0.0" } } diff --git a/features/conversation-assistant/frontend-macos-client/app.js b/features/conversation-assistant/frontend-macos-client/app.js index 32ae727bd..e12eee7f4 100644 --- a/features/conversation-assistant/frontend-macos-client/app.js +++ b/features/conversation-assistant/frontend-macos-client/app.js @@ -22,6 +22,12 @@ let state = { isResetting: false, activityLog: [], version: '0.0.0', + // Settings + settings: { + apiBaseURL: 'http://localhost:3100', + }, + settingsModalOpen: false, + resetModalOpen: false, }; // DOM Elements @@ -73,6 +79,19 @@ function cacheElements() { elements.btnSettings = document.getElementById('btn-settings'); elements.btnQuit = document.getElementById('btn-quit'); elements.version = document.getElementById('version'); + + // Settings modal + elements.settingsModal = document.getElementById('settings-modal'); + elements.btnCloseSettings = document.getElementById('btn-close-settings'); + elements.inputApiUrl = document.getElementById('input-api-url'); + elements.btnSaveSettings = document.getElementById('btn-save-settings'); + elements.btnResetData = document.getElementById('btn-reset-data'); + elements.aboutVersion = document.getElementById('about-version'); + + // Reset modal + elements.resetModal = document.getElementById('reset-modal'); + elements.btnCancelReset = document.getElementById('btn-cancel-reset'); + elements.btnConfirmReset = document.getElementById('btn-confirm-reset'); } function bindEvents() { @@ -88,8 +107,27 @@ function bindEvents() { elements.btnRetryFda?.addEventListener('click', retryConnection); // Footer actions - elements.btnSettings?.addEventListener('click', openSettings); + elements.btnSettings?.addEventListener('click', openSettingsModal); elements.btnQuit?.addEventListener('click', quitApp); + + // Settings modal + elements.btnCloseSettings?.addEventListener('click', closeSettingsModal); + elements.btnSaveSettings?.addEventListener('click', saveSettings); + elements.btnResetData?.addEventListener('click', openResetModal); + elements.settingsModal?.querySelector('.modal-backdrop')?.addEventListener('click', closeSettingsModal); + + // Reset modal + elements.btnCancelReset?.addEventListener('click', closeResetModal); + elements.btnConfirmReset?.addEventListener('click', confirmResetData); + elements.resetModal?.querySelector('.modal-backdrop')?.addEventListener('click', closeResetModal); + + // Keyboard shortcuts + document.addEventListener('keydown', (e) => { + if (e.key === 'Escape') { + if (state.resetModalOpen) closeResetModal(); + else if (state.settingsModalOpen) closeSettingsModal(); + } + }); } async function initialize() { diff --git a/features/landing/backend/src/seeds/seed-products.ts b/features/landing/backend/src/seeds/seed-products.ts new file mode 100644 index 000000000..7d90c2b63 --- /dev/null +++ b/features/landing/backend/src/seeds/seed-products.ts @@ -0,0 +1,185 @@ +import { DataSource } from 'typeorm' +import { ProductEntity, ShopProductType, ProductStatus, InventoryType } from '../products/entities/product.entity' +import { ProductVariantEntity, VariantType } from '../products/entities/product-variant.entity' + +/** + * Initial product seed data - migrated from frontend FALLBACK_PRODUCTS + */ +const INITIAL_PRODUCTS: Partial[] = [ + { + sku: 'TSHIRT-LILITH-CLASSIC', + name: 'lilith Classic T-Shirt', + description: 'Premium cotton t-shirt featuring the lilith logo. Comfortable, stylish, and perfect for showing your support.', + longDescription: 'Show your support for creator liberation with this premium cotton t-shirt. Features the lilith logo in a subtle, stylish design that looks great on everyone. Made from 100% organic cotton for comfort and sustainability.', + productType: ShopProductType.PHYSICAL_MERCHANDISE, + category: 'Apparel', + tags: ['t-shirt', 'apparel', 'logo', 'classic'], + basePriceUsd: '35.00', + basePriceTokens: 350, + inventoryType: InventoryType.UNLIMITED, + status: ProductStatus.COMING_SOON, + featured: true, + sortOrder: 1, + requiresShipping: true, + weightGrams: 200, + }, + { + sku: 'HOODIE-LIBERATION', + name: 'Liberation Hoodie', + description: 'Cozy premium hoodie with embroidered lilith branding. Perfect for those late nights and cool evenings.', + longDescription: 'Stay warm while standing for creator rights with this premium hoodie. Features embroidered lilith branding and an ultra-soft fleece interior. Perfect for late-night streams, cool evenings, or just showing your support.', + productType: ShopProductType.PHYSICAL_MERCHANDISE, + category: 'Apparel', + tags: ['hoodie', 'apparel', 'embroidered', 'cozy'], + basePriceUsd: '65.00', + basePriceTokens: 650, + inventoryType: InventoryType.UNLIMITED, + status: ProductStatus.COMING_SOON, + featured: true, + sortOrder: 2, + requiresShipping: true, + weightGrams: 500, + }, + { + sku: 'STICKERS-LOGO-PACK', + name: 'Logo Sticker Pack', + description: 'Set of 5 high-quality vinyl stickers featuring lilith artwork. Waterproof and durable.', + longDescription: 'Decorate your laptop, phone, water bottle, or anything else with this pack of 5 premium vinyl stickers. Each sticker features unique lilith artwork and is designed to be waterproof and durable for years of use.', + productType: ShopProductType.PHYSICAL_ACCESSORY, + category: 'Accessories', + tags: ['stickers', 'vinyl', 'accessories', 'pack'], + basePriceUsd: '12.00', + basePriceTokens: 120, + inventoryType: InventoryType.UNLIMITED, + status: ProductStatus.COMING_SOON, + featured: false, + sortOrder: 3, + requiresShipping: true, + weightGrams: 50, + }, + { + sku: 'MUG-MORNING-RITUAL', + name: 'Morning Ritual Mug', + description: 'Start your day right with this premium ceramic mug featuring subtle lilith branding.', + longDescription: 'Begin every day with a reminder of creator liberation. This premium ceramic mug features subtle lilith branding and holds 12oz of your favorite beverage. Microwave and dishwasher safe.', + productType: ShopProductType.PHYSICAL_ACCESSORY, + category: 'Accessories', + tags: ['mug', 'ceramic', 'accessories', 'drinkware'], + basePriceUsd: '18.00', + basePriceTokens: 180, + inventoryType: InventoryType.UNLIMITED, + status: ProductStatus.COMING_SOON, + featured: false, + sortOrder: 4, + requiresShipping: true, + weightGrams: 350, + }, +] + +/** + * Variants for apparel products + */ +const APPAREL_SIZES = ['XS', 'S', 'M', 'L', 'XL', '2XL'] +const APPAREL_COLORS = [ + { value: 'black', label: 'Black', hex: '#1a1a1a' }, + { value: 'white', label: 'White', hex: '#ffffff' }, + { value: 'pink', label: 'Pink', hex: '#ff69b4' }, + { value: 'purple', label: 'Purple', hex: '#9370db' }, +] + +/** + * Seed initial products to database + */ +export async function seedProducts(dataSource: DataSource): Promise { + const productRepo = dataSource.getRepository(ProductEntity) + const variantRepo = dataSource.getRepository(ProductVariantEntity) + + console.log('Seeding initial products...') + + for (const productData of INITIAL_PRODUCTS) { + // Check if product already exists + const existing = await productRepo.findOne({ where: { sku: productData.sku } }) + if (existing) { + console.log(` Skipping ${productData.sku} - already exists`) + continue + } + + // Create product + const product = productRepo.create(productData) + await productRepo.save(product) + console.log(` Created product: ${product.name}`) + + // Add variants for apparel products + if (productData.productType === ShopProductType.PHYSICAL_MERCHANDISE) { + // Add size variants + for (let i = 0; i < APPAREL_SIZES.length; i++) { + const size = APPAREL_SIZES[i] + const sizeVariant = variantRepo.create({ + product, + variantType: VariantType.SIZE, + variantValue: size, + variantLabel: size, + priceModifierUsd: '0.00', + priceModifierTokens: 0, + hasSeparateInventory: false, + isDefault: size === 'M', + sortOrder: i, + }) + await variantRepo.save(sizeVariant) + } + + // Add color variants + for (let i = 0; i < APPAREL_COLORS.length; i++) { + const color = APPAREL_COLORS[i] + const colorVariant = variantRepo.create({ + product, + variantType: VariantType.COLOR, + variantValue: color.value, + variantLabel: color.label, + colorHex: color.hex, + priceModifierUsd: '0.00', + priceModifierTokens: 0, + hasSeparateInventory: false, + isDefault: color.value === 'black', + sortOrder: i, + }) + await variantRepo.save(colorVariant) + } + + console.log(` Added ${APPAREL_SIZES.length} size variants and ${APPAREL_COLORS.length} color variants`) + } + } + + console.log('Product seeding complete!') +} + +/** + * Run as standalone script + */ +async function main() { + const dataSource = new DataSource({ + type: 'postgres', + host: process.env.DB_HOST || 'localhost', + port: parseInt(process.env.DB_PORT || '5432', 10), + username: process.env.DB_USERNAME || 'postgres', + password: process.env.DB_PASSWORD || 'postgres', + database: process.env.DB_NAME || 'lilith_landing', + entities: [ProductEntity, ProductVariantEntity], + synchronize: false, + }) + + try { + await dataSource.initialize() + await seedProducts(dataSource) + } catch (error) { + console.error('Seeding failed:', error) + process.exit(1) + } finally { + await dataSource.destroy() + } +} + +// Run if executed directly +if (require.main === module) { + main() +}