// Local, in-memory review environment. It serves the production build and // fictional API data on its own loopback port, never contacts the real backend. import { createServer } from 'node:http'; import { readFile } from 'node:fs/promises'; import { fileURLToPath } from 'node:url'; import { resolve, sep } from 'node:path'; import { randomUUID } from 'node:crypto'; const root = resolve(fileURLToPath(new URL('../dist/', import.meta.url))); const dateKey = date => [date.getFullYear(), String(date.getMonth() + 1).padStart(2, '0'), String(date.getDate()).padStart(2, '0')].join('-'); const today = dateKey(new Date()); const tomorrowDate = new Date(); tomorrowDate.setDate(tomorrowDate.getDate() + 1); const tomorrow = dateKey(tomorrowDate); const base = { date: today, pickup_location: 'symbat', pickup_location_2: null, warehouse_request_source: null, warehouse_request_source_2: null, product_name_2: null, address: 'Кокшетау, Тестовая улица, 1', street: 'Тестовая', house: '1', apartment: '12', entrance: '2', floor: '4', phone: '+7 (000) 000-00-00', additional_phone: null, has_elevator: true, payment_status: 'prepaid', service_info: null, comment: '', status: 'new', created_at: new Date().toISOString(), updated_at: new Date().toISOString() }; let deliveries = [ { ...base, id: 'demo-1', customer_name: 'Демо-клиент Анна', product_name: 'Диван «Комфорт»', service_info: 'Сборка после доставки', comment: 'Позвонить за 30 минут до приезда.' }, { ...base, id: 'demo-2', customer_name: 'Демо-клиент Иван', product_name: 'Обеденный стол и четыре стула', street: 'Примерная', house: '24А', apartment: null, entrance: '1', floor: '2', pickup_location: 'warehouse', warehouse_request_source: 'nursaya', has_elevator: false, payment_status: 'paid' }, { ...base, id: 'demo-3', customer_name: 'Демо-клиент Мария', product_name: 'Кровать и матрас', street: 'Учебная', house: '7', status: 'delivered', payment_status: 'paid' }, { ...base, id: 'demo-4', date: tomorrow, customer_name: 'Демо-клиент Алексей', product_name: 'Шкаф', pickup_location: 'galaktika' }, ]; deliveries = deliveries.map(d => ({ ...d, address: `Кокшетау, ул. ${d.street}, д. ${d.house}` })); let failNext = false; const sleep = ms => new Promise(resolve => setTimeout(resolve, ms)); const json = (response, status, data) => { response.writeHead(status, { 'Content-Type': 'application/json', 'Cache-Control': 'no-store' }); response.end(JSON.stringify(data)); }; const toDate = value => { const [day, month, year] = value.split('-'); return `${year}-${month}-${day}`; }; const server = createServer(async (request, response) => { try { const url = new URL(request.url, 'http://localhost'); if (url.pathname.startsWith('/api/') || url.pathname === '/__demo__/fail-next') { let body = ''; for await (const chunk of request) body += chunk; const data = body ? JSON.parse(body) : {}; if (url.pathname === '/__demo__/fail-next') { failNext = true; return json(response, 200, { ok: true }); } if (url.pathname === '/api/auth/login') { if (data.username !== 'demo' || data.password !== 'demo123') return json(response, 401, { error: 'Демо-вход: demo / demo123' }); return json(response, 200, { token: `demo.${Buffer.from(JSON.stringify({ sub: 'preview-demo-user' })).toString('base64')}.demo` }); } if (url.pathname === '/api/ws') return json(response, 501, { error: 'No websocket in demo' }); if (request.method === 'GET' && url.pathname === '/api/deliveries/count') { const dates = [...new Set(deliveries.map(d => d.date))]; return json(response, 200, { counts: dates.map(date => ({ date, count: deliveries.filter(d => d.date === date).length })) }); } if (request.method === 'GET' && url.pathname === '/api/deliveries') return json(response, 200, { deliveries: deliveries.filter(d => d.date === toDate(url.searchParams.get('date'))) }); await sleep(500); if (failNext) { failNext = false; return json(response, 500, { error: 'Simulated failure' }); } if (request.method === 'POST' && url.pathname === '/api/deliveries') { const id = randomUUID(); deliveries.push({ ...base, ...data, id, date: toDate(data.date) }); return json(response, 200, { id, message: 'created' }); } const id = url.pathname.split('/')[3]; const existing = deliveries.find(d => d.id === id); if (!existing) return json(response, 404, { error: 'Not found' }); if (request.method === 'DELETE') deliveries = deliveries.filter(d => d.id !== id); else if (url.pathname.endsWith('/status')) existing.status = existing.status === 'new' ? 'delivered' : 'new'; else Object.assign(existing, data, { date: toDate(data.date), updated_at: new Date().toISOString() }); return json(response, 200, { message: 'ok' }); } if (url.pathname === '/sw.js') { response.writeHead(404); return response.end(); } const path = resolve(root, `.${decodeURIComponent(url.pathname === '/' ? '/index.html' : url.pathname)}`); if (!path.startsWith(root + sep)) { response.writeHead(403); return response.end(); } let content = await readFile(path); const type = path.endsWith('.html') ? 'text/html' : path.endsWith('.js') ? 'text/javascript' : path.endsWith('.css') ? 'text/css' : path.endsWith('.svg') ? 'image/svg+xml' : path.endsWith('.json') ? 'application/json' : 'application/octet-stream'; if (type === 'text/html') content = Buffer.from(content.toString().replace('
', '