import { useState, useEffect, useMemo } from 'react'; import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; import { useTranslation } from 'react-i18next'; 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, HardDrive, } from 'lucide-react'; import { api } from '../api/client'; import { parseUTCDate } from '../utils/date'; import type { SlicerSetting, SlicerSettingsResponse, SlicerSettingDetail, SlicerSettingCreate, Printer, FieldDefinition, Permission } from '../api/client'; import { Card, CardContent } from '../components/Card'; import { Button } from '../components/Button'; import { useToast } from '../contexts/ToastContext'; import { useAuth } from '../contexts/AuthContext'; import { KProfilesView } from '../components/KProfilesView'; import { LocalProfilesView } from '../components/LocalProfilesView'; type TFunction = (key: string, options?: Record) => string; type ProfileTab = 'cloud' | 'local' | '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, t: TFunction): string { const date = parseUTCDate(dateStr); if (!date) return ''; 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 t('profiles.time.justNow'); if (diffMins < 60) return t('profiles.time.minsAgo', { count: diffMins }); if (diffHours < 24) return t('profiles.time.hoursAgo', { count: diffHours }); if (diffDays < 7) return t('profiles.time.daysAgo', { count: diffDays }); return date.toLocaleDateString(); } // ============================================================================ // LOGIN FORM // ============================================================================ function LoginForm({ onSuccess, t }: { onSuccess: () => void; t: TFunction }) { 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 [verificationType, setVerificationType] = useState<'email' | 'totp' | null>(null); const [tfaKey, setTfaKey] = useState(null); const loginMutation = useMutation({ mutationFn: () => api.cloudLogin(email, password, region), onSuccess: (result) => { if (result.success) { showToast(t('profiles.login.toast.loggedIn')); onSuccess(); } else if (result.needs_verification) { setVerificationType(result.verification_type || 'email'); setTfaKey(result.tfa_key || null); if (result.verification_type === 'totp') { showToast(t('profiles.login.toast.enterTotp')); } else { showToast(t('profiles.login.toast.codeSent')); } setStep('code'); } else { showToast(result.message, 'error'); } }, onError: (error: Error) => showToast(error.message, 'error'), }); const verifyMutation = useMutation({ mutationFn: () => api.cloudVerify(email, code, tfaKey || undefined), onSuccess: (result) => { if (result.success) { showToast(t('profiles.login.toast.loggedIn')); onSuccess(); } else { showToast(result.message, 'error'); } }, onError: (error: Error) => showToast(error.message, 'error'), }); const tokenMutation = useMutation({ mutationFn: () => api.cloudSetToken(token), onSuccess: () => { showToast(t('profiles.login.toast.tokenSet')); 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 (

{t('profiles.login.title')}

{t('profiles.login.subtitle')}

{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' && (

{verificationType === 'totp' ? t('profiles.login.enterTotpHint') : t('profiles.login.checkEmail', { email })}

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' && (

{t('profiles.login.accessTokenHint')}