import { useState, useEffect, useMemo } from 'react'; import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; import { Cloud, LogIn, LogOut, Loader2, Settings2, Printer as PrinterIcon, Droplet, X, Key, RefreshCw, Gauge, Pencil, Trash2, Save, AlertTriangle, Search, Plus, Copy, Clock, Layers, Filter, ChevronDown, ArrowUp, Upload, Download, Sparkles, Check, AlertCircle, Code, Sliders, List, Eye, EyeOff, GitCompare, ArrowRight, Equal, Minus as MinusIcon, Plus as PlusIcon, } from 'lucide-react'; import { api } from '../api/client'; import type { SlicerSetting, SlicerSettingsResponse, SlicerSettingDetail, SlicerSettingCreate, Printer, FieldDefinition } from '../api/client'; import { Card, CardContent } from '../components/Card'; import { Button } from '../components/Button'; import { useToast } from '../contexts/ToastContext'; import { KProfilesView } from '../components/KProfilesView'; type ProfileTab = 'cloud' | 'kprofiles'; type LoginStep = 'email' | 'code' | 'token'; type PresetType = 'all' | 'filament' | 'printer' | 'process'; // Extract metadata from preset name or inherits field function extractMetadata(name: string, inherits?: string): { printer: string | null; nozzle: string | null; layerHeight: string | null; filamentType: string | null; } { const searchIn = `${name} ${inherits || ''}`; // Extract printer (e.g., "X1C", "P1S", "A1", "H2D") const printerMatch = searchIn.match(/@?\s*(?:BBL\s+)?(?:Bambu\s+Lab\s+)?([XPAH][1-9][A-Z]?(?:\s*(?:Carbon|mini))?|H2D)/i); const printer = printerMatch ? printerMatch[1].trim() : null; // Extract nozzle size (e.g., "0.4 nozzle", "0.6mm") const nozzleMatch = searchIn.match(/(\d+\.?\d*)\s*(?:mm\s*)?nozzle|nozzle\s*(\d+\.?\d*)/i); const nozzle = nozzleMatch ? (nozzleMatch[1] || nozzleMatch[2]) + 'mm' : null; // Extract layer height (e.g., "0.20mm", "0.08mm Extra Fine") const layerMatch = searchIn.match(/(\d+\.?\d*)mm\s*(?:Standard|Fine|Extra Fine|Draft|Quality)?/i); const layerHeight = layerMatch ? layerMatch[1] + 'mm' : null; // Extract filament type (e.g., "PLA", "PETG", "ABS", "TPU") const filamentMatch = searchIn.match(/\b(PLA|PETG|ABS|ASA|TPU|PC|PA|PVA|HIPS|PP|PET(?:-?CF)?|PA(?:-?CF)?|PLA(?:-?CF)?)\b/i); const filamentType = filamentMatch ? filamentMatch[1].toUpperCase() : null; return { printer, nozzle, layerHeight, filamentType }; } // Check if preset is user-created (editable) function isUserPreset(settingId: string): boolean { return /^(P[FPM]US|PF\d|PP\d)/.test(settingId); } // Format relative time function formatRelativeTime(dateStr: string): string { const date = new Date(dateStr); const now = new Date(); const diffMs = now.getTime() - date.getTime(); const diffMins = Math.floor(diffMs / 60000); const diffHours = Math.floor(diffMs / 3600000); const diffDays = Math.floor(diffMs / 86400000); if (diffMins < 1) return 'Just now'; if (diffMins < 60) return `${diffMins}m ago`; if (diffHours < 24) return `${diffHours}h ago`; if (diffDays < 7) return `${diffDays}d ago`; return date.toLocaleDateString(); } // ============================================================================ // LOGIN FORM // ============================================================================ function LoginForm({ onSuccess }: { onSuccess: () => void }) { const { showToast } = useToast(); const [step, setStep] = useState('email'); const [email, setEmail] = useState(''); const [password, setPassword] = useState(''); const [code, setCode] = useState(''); const [token, setToken] = useState(''); const [region, setRegion] = useState('global'); const loginMutation = useMutation({ mutationFn: () => api.cloudLogin(email, password, region), onSuccess: (result) => { if (result.success) { showToast('Logged in successfully'); onSuccess(); } else if (result.needs_verification) { showToast('Verification code sent to your email'); setStep('code'); } else { showToast(result.message, 'error'); } }, onError: (error: Error) => showToast(error.message, 'error'), }); const verifyMutation = useMutation({ mutationFn: () => api.cloudVerify(email, code), onSuccess: (result) => { if (result.success) { showToast('Logged in successfully'); onSuccess(); } else { showToast(result.message, 'error'); } }, onError: (error: Error) => showToast(error.message, 'error'), }); const tokenMutation = useMutation({ mutationFn: () => api.cloudSetToken(token), onSuccess: () => { showToast('Token set successfully'); onSuccess(); }, onError: (error: Error) => showToast(error.message, 'error'), }); const handleSubmit = (e: React.FormEvent) => { e.preventDefault(); if (step === 'email') loginMutation.mutate(); else if (step === 'code') verifyMutation.mutate(); else if (step === 'token') tokenMutation.mutate(); }; const isPending = loginMutation.isPending || verifyMutation.isPending || tokenMutation.isPending; return (

Connect to Bambu Cloud

Sync your slicer presets across devices

{step === 'email' && ( <>
setEmail(e.target.value)} className="w-full px-3 py-2 bg-bambu-dark border border-bambu-dark-tertiary rounded-lg text-white placeholder-bambu-gray-dark focus:border-bambu-green focus:outline-none" placeholder="your@email.com" required />
setPassword(e.target.value)} className="w-full px-3 py-2 bg-bambu-dark border border-bambu-dark-tertiary rounded-lg text-white placeholder-bambu-gray-dark focus:border-bambu-green focus:outline-none" placeholder="••••••••" required />
)} {step === 'code' && (

Check your email ({email}) for a 6-digit code

setCode(e.target.value.replace(/\D/g, '').slice(0, 6))} className="w-full px-3 py-3 bg-bambu-dark border border-bambu-dark-tertiary rounded-lg text-white text-center text-2xl tracking-widest font-mono focus:border-bambu-green focus:outline-none" placeholder="000000" maxLength={6} required />
)} {step === 'token' && (

Paste your Bambu Lab access token (from Bambu Studio)