Browse Source

Minor layout improvements

maziggy 3 months ago
parent
commit
d71e56c3e6

+ 236 - 190
frontend/src/components/EmailSettings.tsx

@@ -9,6 +9,18 @@ import { Button } from './Button';
 import { useToast } from '../contexts/ToastContext';
 import { useEffect } from 'react';
 
+const SECURITY_PORT_MAP: Record<string, number> = {
+  starttls: 587,
+  ssl: 465,
+  none: 25,
+};
+
+const PORT_SECURITY_MAP: Record<number, string> = {
+  587: 'starttls',
+  465: 'ssl',
+  25: 'none',
+};
+
 export function EmailSettings() {
   const { t } = useTranslation();
   const { showToast } = useToast();
@@ -48,6 +60,31 @@ export function EmailSettings() {
     }
   }, [existingSettings]);
 
+  const handleSecurityChange = (security: 'starttls' | 'ssl' | 'none') => {
+    setSMTPSettings({
+      ...smtpSettings,
+      smtp_security: security,
+      smtp_port: SECURITY_PORT_MAP[security],
+    });
+  };
+
+  const handlePortChange = (port: number) => {
+    const matchedSecurity = PORT_SECURITY_MAP[port];
+    setSMTPSettings({
+      ...smtpSettings,
+      smtp_port: port,
+      ...(matchedSecurity ? { smtp_security: matchedSecurity as 'starttls' | 'ssl' | 'none' } : {}),
+    });
+  };
+
+  const handleAuthChange = (enabled: boolean) => {
+    setSMTPSettings({
+      ...smtpSettings,
+      smtp_auth_enabled: enabled,
+      ...(!enabled ? { smtp_username: '', smtp_password: '' } : {}),
+    });
+  };
+
   // Save SMTP settings
   const saveMutation = useMutation({
     mutationFn: (settings: SMTPSettings) => api.saveSMTPSettings(settings),
@@ -141,145 +178,106 @@ export function EmailSettings() {
     );
   }
 
+  const advancedEnabled = advancedAuthStatus?.advanced_auth_enabled ?? false;
+  const inputClasses = "w-full px-4 py-3 bg-bambu-dark-secondary border border-bambu-dark-tertiary rounded-lg text-white placeholder-bambu-gray focus:outline-none focus:ring-2 focus:ring-bambu-green/50 focus:border-bambu-green transition-colors";
+  const disabledInputClasses = "w-full px-4 py-3 bg-bambu-dark-secondary border border-bambu-dark-tertiary rounded-lg text-white/40 placeholder-bambu-gray/40 cursor-not-allowed";
+
   return (
     <div className="space-y-6">
-      {/* Advanced Authentication Toggle - Only show when SMTP is configured */}
-      {advancedAuthStatus?.smtp_configured && (
-        <Card>
-          <CardHeader>
-            <div className="flex items-center justify-between">
-              <div className="flex items-center gap-2">
-                <Mail className="w-5 h-5 text-bambu-green" />
-                <h2 className="text-lg font-semibold text-white">
-                  {t('settings.email.advancedAuth') || 'Advanced Authentication'}
-                </h2>
-              </div>
-              <Button
-                onClick={handleToggleAdvancedAuth}
-                disabled={toggleAdvancedAuthMutation.isPending}
-                variant={advancedAuthStatus?.advanced_auth_enabled ? 'danger' : 'primary'}
-              >
-                {advancedAuthStatus?.advanced_auth_enabled ? (
-                  <>
-                    <Unlock className="w-4 h-4" />
-                    {t('settings.email.disable') || 'Disable'}
-                  </>
-                ) : (
-                  <>
-                    <Lock className="w-4 h-4" />
-                    {t('settings.email.enable') || 'Enable'}
-                  </>
-                )}
-              </Button>
+      {/* Advanced Authentication Toggle - Always visible */}
+      <Card>
+        <CardHeader>
+          <div className="flex items-center justify-between">
+            <div className="flex items-center gap-2">
+              <Mail className="w-5 h-5 text-bambu-green" />
+              <h2 className="text-lg font-semibold text-white">
+                {t('settings.email.advancedAuth') || 'Advanced Authentication'}
+              </h2>
             </div>
-          </CardHeader>
-          <CardContent>
-            <div className="space-y-4">
-              {advancedAuthStatus?.advanced_auth_enabled ? (
-                <div className="bg-green-500/10 border border-green-500/30 rounded-lg p-4">
-                  <div className="flex items-start gap-3">
-                    <CheckCircle className="w-5 h-5 text-green-400 mt-0.5 flex-shrink-0" />
-                    <div className="space-y-2">
-                      <p className="text-white font-medium">
-                        {t('settings.email.advancedAuthEnabled') || 'Advanced Authentication is enabled'}
-                      </p>
-                      <ul className="text-sm text-green-300 space-y-1 list-disc list-inside">
-                        <li>{t('settings.email.feature1') || 'Passwords are auto-generated and emailed to new users'}</li>
-                        <li>{t('settings.email.feature2') || 'Users can login with username or email'}</li>
-                        <li>{t('settings.email.feature3') || 'Forgot password feature is available'}</li>
-                        <li>{t('settings.email.feature4') || 'Admins can reset user passwords via email'}</li>
-                      </ul>
-                    </div>
-                  </div>
-                </div>
+            <Button
+              onClick={handleToggleAdvancedAuth}
+              disabled={toggleAdvancedAuthMutation.isPending}
+              variant={advancedEnabled ? 'danger' : 'primary'}
+            >
+              {advancedEnabled ? (
+                <>
+                  <Unlock className="w-4 h-4" />
+                  {t('settings.email.disable') || 'Disable'}
+                </>
               ) : (
-                <div className="bg-yellow-500/10 border border-yellow-500/30 rounded-lg p-4">
-                  <div className="flex items-start gap-3">
-                    <AlertTriangle className="w-5 h-5 text-yellow-400 mt-0.5 flex-shrink-0" />
-                    <div className="space-y-2">
-                      <p className="text-white font-medium">
-                        {t('settings.email.advancedAuthDisabled') || 'Advanced Authentication is disabled'}
-                      </p>
-                      <p className="text-sm text-yellow-300">
-                        {t('settings.email.advancedAuthDisabledDesc') || 'Enable advanced authentication to activate email-based features for user management.'}
-                      </p>
-                    </div>
-                  </div>
-                </div>
+                <>
+                  <Lock className="w-4 h-4" />
+                  {t('settings.email.enable') || 'Enable'}
+                </>
               )}
-            </div>
-          </CardContent>
-        </Card>
-      )}
-
-      {/* SMTP Configuration */}
-      <Card>
-        <CardHeader>
-          <h2 className="text-lg font-semibold text-white">
-            {t('settings.email.smtpSettings') || 'SMTP Configuration'}
-          </h2>
+            </Button>
+          </div>
         </CardHeader>
         <CardContent>
           <div className="space-y-4">
-            <div className="grid grid-cols-1 md:grid-cols-2 gap-4">
-              <div>
-                <label className="block text-sm font-medium text-white mb-2">
-                  {t('settings.email.smtpHost') || 'SMTP Server'} *
-                </label>
-                <input
-                  type="text"
-                  value={smtpSettings.smtp_host}
-                  onChange={(e) => setSMTPSettings({ ...smtpSettings, smtp_host: e.target.value })}
-                  placeholder="smtp.gmail.com"
-                  className="w-full px-4 py-3 bg-bambu-dark-secondary border border-bambu-dark-tertiary rounded-lg text-white placeholder-bambu-gray focus:outline-none focus:ring-2 focus:ring-bambu-green/50 focus:border-bambu-green transition-colors"
-                />
+            {advancedEnabled ? (
+              <div className="bg-green-500/10 border border-green-500/30 rounded-lg p-4">
+                <div className="flex items-start gap-3">
+                  <CheckCircle className="w-5 h-5 text-green-400 mt-0.5 flex-shrink-0" />
+                  <div className="space-y-2">
+                    <p className="text-white font-medium">
+                      {t('settings.email.advancedAuthEnabled') || 'Advanced Authentication is enabled'}
+                    </p>
+                    <ul className="text-sm text-green-300 space-y-1 list-disc list-inside">
+                      <li>{t('settings.email.feature1') || 'Passwords are auto-generated and emailed to new users'}</li>
+                      <li>{t('settings.email.feature2') || 'Users can login with username or email'}</li>
+                      <li>{t('settings.email.feature3') || 'Forgot password feature is available'}</li>
+                      <li>{t('settings.email.feature4') || 'Admins can reset user passwords via email'}</li>
+                    </ul>
+                  </div>
+                </div>
               </div>
-              <div>
-                <label className="block text-sm font-medium text-white mb-2">
-                  {t('settings.email.smtpPort') || 'SMTP Port'}
-                </label>
-                <input
-                  type="number"
-                  value={smtpSettings.smtp_port}
-                  onChange={(e) => setSMTPSettings({ ...smtpSettings, smtp_port: parseInt(e.target.value) || 587 })}
-                  placeholder="587"
-                  className="w-full px-4 py-3 bg-bambu-dark-secondary border border-bambu-dark-tertiary rounded-lg text-white placeholder-bambu-gray focus:outline-none focus:ring-2 focus:ring-bambu-green/50 focus:border-bambu-green transition-colors"
-                />
+            ) : (
+              <div className="bg-yellow-500/10 border border-yellow-500/30 rounded-lg p-4">
+                <div className="flex items-start gap-3">
+                  <AlertTriangle className="w-5 h-5 text-yellow-400 mt-0.5 flex-shrink-0" />
+                  <div className="space-y-2">
+                    <p className="text-white font-medium">
+                      {t('settings.email.advancedAuthDisabled') || 'Advanced Authentication is disabled'}
+                    </p>
+                    <p className="text-sm text-yellow-300">
+                      {t('settings.email.advancedAuthDisabledDesc') || 'Enable advanced authentication to activate email-based features for user management.'}
+                    </p>
+                  </div>
+                </div>
               </div>
-            </div>
+            )}
+          </div>
+        </CardContent>
+      </Card>
 
-            <div className="grid grid-cols-1 md:grid-cols-2 gap-4">
-              <div>
-                <label className="block text-sm font-medium text-white mb-2">
-                  {t('settings.email.security') || 'Security'}
-                </label>
-                <select
-                  value={smtpSettings.smtp_security}
-                  onChange={(e) => setSMTPSettings({ ...smtpSettings, smtp_security: e.target.value as 'starttls' | 'ssl' | 'none' })}
-                  className="w-full px-4 py-3 bg-bambu-dark-secondary border border-bambu-dark-tertiary rounded-lg text-white focus:outline-none focus:ring-2 focus:ring-bambu-green/50 focus:border-bambu-green transition-colors"
-                >
-                  <option value="starttls">{t('settings.email.securityOptions.starttls')}</option>
-                  <option value="ssl">{t('settings.email.securityOptions.ssl')}</option>
-                  <option value="none">{t('settings.email.securityOptions.none')}</option>
-                </select>
-              </div>
+      {/* SMTP Configuration - dimmed when advanced auth is disabled */}
+      <div className={!advancedEnabled ? 'opacity-50 pointer-events-none' : ''}>
+        <Card>
+          <CardHeader>
+            <h2 className="text-lg font-semibold text-white">
+              {t('settings.email.smtpSettings') || 'SMTP Configuration'}
+            </h2>
+          </CardHeader>
+          <CardContent>
+            <div className="space-y-4">
+              {/* Authentication - at the top */}
               <div>
                 <label className="block text-sm font-medium text-white mb-2">
                   {t('settings.email.authentication') || 'Authentication'}
                 </label>
                 <select
                   value={smtpSettings.smtp_auth_enabled ? 'true' : 'false'}
-                  onChange={(e) => setSMTPSettings({ ...smtpSettings, smtp_auth_enabled: e.target.value === 'true' })}
-                  className="w-full px-4 py-3 bg-bambu-dark-secondary border border-bambu-dark-tertiary rounded-lg text-white focus:outline-none focus:ring-2 focus:ring-bambu-green/50 focus:border-bambu-green transition-colors"
+                  onChange={(e) => handleAuthChange(e.target.value === 'true')}
+                  className={inputClasses}
                 >
                   <option value="true">{t('settings.email.authOptions.enabled')}</option>
                   <option value="false">{t('settings.email.authOptions.disabled')}</option>
                 </select>
               </div>
-            </div>
 
-            {smtpSettings.smtp_auth_enabled && (
-              <>
+              {/* Username / Password - dimmed when auth disabled */}
+              <div className={`grid grid-cols-1 md:grid-cols-2 gap-4 transition-opacity ${!smtpSettings.smtp_auth_enabled ? 'opacity-40 pointer-events-none' : ''}`}>
                 <div>
                   <label className="block text-sm font-medium text-white mb-2">
                     {t('settings.email.username') || 'Username'}
@@ -289,10 +287,10 @@ export function EmailSettings() {
                     value={smtpSettings.smtp_username || ''}
                     onChange={(e) => setSMTPSettings({ ...smtpSettings, smtp_username: e.target.value })}
                     placeholder="your.email@gmail.com"
-                    className="w-full px-4 py-3 bg-bambu-dark-secondary border border-bambu-dark-tertiary rounded-lg text-white placeholder-bambu-gray focus:outline-none focus:ring-2 focus:ring-bambu-green/50 focus:border-bambu-green transition-colors"
+                    disabled={!smtpSettings.smtp_auth_enabled}
+                    className={smtpSettings.smtp_auth_enabled ? inputClasses : disabledInputClasses}
                   />
                 </div>
-
                 <div>
                   <label className="block text-sm font-medium text-white mb-2">
                     {t('settings.email.password') || 'Password'}
@@ -302,100 +300,148 @@ export function EmailSettings() {
                     value={smtpSettings.smtp_password || ''}
                     onChange={(e) => setSMTPSettings({ ...smtpSettings, smtp_password: e.target.value })}
                     placeholder={existingSettings ? '••••••••' : 'App password'}
-                    className="w-full px-4 py-3 bg-bambu-dark-secondary border border-bambu-dark-tertiary rounded-lg text-white placeholder-bambu-gray focus:outline-none focus:ring-2 focus:ring-bambu-green/50 focus:border-bambu-green transition-colors"
+                    disabled={!smtpSettings.smtp_auth_enabled}
+                    className={smtpSettings.smtp_auth_enabled ? inputClasses : disabledInputClasses}
                   />
                 </div>
-              </>
-            )}
+              </div>
+
+              {/* SMTP Server / Port */}
+              <div className="grid grid-cols-1 md:grid-cols-2 gap-4">
+                <div>
+                  <label className="block text-sm font-medium text-white mb-2">
+                    {t('settings.email.smtpHost') || 'SMTP Server'} *
+                  </label>
+                  <input
+                    type="text"
+                    value={smtpSettings.smtp_host}
+                    onChange={(e) => setSMTPSettings({ ...smtpSettings, smtp_host: e.target.value })}
+                    placeholder="smtp.gmail.com"
+                    className={inputClasses}
+                  />
+                </div>
+                <div>
+                  <label className="block text-sm font-medium text-white mb-2">
+                    {t('settings.email.smtpPort') || 'SMTP Port'}
+                  </label>
+                  <input
+                    type="number"
+                    value={smtpSettings.smtp_port}
+                    onChange={(e) => handlePortChange(parseInt(e.target.value) || 587)}
+                    placeholder="587"
+                    className={inputClasses}
+                  />
+                </div>
+              </div>
 
-            <div className="grid grid-cols-1 md:grid-cols-2 gap-4">
+              {/* Security */}
               <div>
                 <label className="block text-sm font-medium text-white mb-2">
-                  {t('settings.email.fromEmail') || 'From Email'} *
+                  {t('settings.email.security') || 'Security'}
                 </label>
-                <input
-                  type="email"
-                  value={smtpSettings.smtp_from_email}
-                  onChange={(e) => setSMTPSettings({ ...smtpSettings, smtp_from_email: e.target.value })}
-                  placeholder="your@email.com"
-                  className="w-full px-4 py-3 bg-bambu-dark-secondary border border-bambu-dark-tertiary rounded-lg text-white placeholder-bambu-gray focus:outline-none focus:ring-2 focus:ring-bambu-green/50 focus:border-bambu-green transition-colors"
-                />
+                <select
+                  value={smtpSettings.smtp_security}
+                  onChange={(e) => handleSecurityChange(e.target.value as 'starttls' | 'ssl' | 'none')}
+                  className={inputClasses}
+                >
+                  <option value="starttls">{t('settings.email.securityOptions.starttls')}</option>
+                  <option value="ssl">{t('settings.email.securityOptions.ssl')}</option>
+                  <option value="none">{t('settings.email.securityOptions.none')}</option>
+                </select>
               </div>
+
+              {/* From Email / Name */}
+              <div className="grid grid-cols-1 md:grid-cols-2 gap-4">
+                <div>
+                  <label className="block text-sm font-medium text-white mb-2">
+                    {t('settings.email.fromEmail') || 'From Email'} *
+                  </label>
+                  <input
+                    type="email"
+                    value={smtpSettings.smtp_from_email}
+                    onChange={(e) => setSMTPSettings({ ...smtpSettings, smtp_from_email: e.target.value })}
+                    placeholder="your@email.com"
+                    className={inputClasses}
+                  />
+                </div>
+                <div>
+                  <label className="block text-sm font-medium text-white mb-2">
+                    {t('settings.email.fromName') || 'From Name'}
+                  </label>
+                  <input
+                    type="text"
+                    value={smtpSettings.smtp_from_name}
+                    onChange={(e) => setSMTPSettings({ ...smtpSettings, smtp_from_name: e.target.value })}
+                    placeholder="BamBuddy"
+                    className={inputClasses}
+                  />
+                </div>
+              </div>
+
+              <div className="flex gap-2">
+                <Button
+                  onClick={handleSave}
+                  disabled={saveMutation.isPending}
+                  className="flex-1"
+                >
+                  {saveMutation.isPending ? (
+                    <>
+                      <Loader2 className="w-4 h-4 animate-spin" />
+                      {t('settings.email.saving') || 'Saving...'}
+                    </>
+                  ) : (
+                    t('settings.email.save') || 'Save Settings'
+                  )}
+                </Button>
+              </div>
+            </div>
+          </CardContent>
+        </Card>
+      </div>
+
+      {/* Test SMTP - dimmed when advanced auth is disabled */}
+      <div className={!advancedEnabled ? 'opacity-50 pointer-events-none' : ''}>
+        <Card>
+          <CardHeader>
+            <h2 className="text-lg font-semibold text-white">
+              {t('settings.email.testConnection') || 'Test SMTP Connection'}
+            </h2>
+          </CardHeader>
+          <CardContent>
+            <div className="space-y-4">
               <div>
                 <label className="block text-sm font-medium text-white mb-2">
-                  {t('settings.email.fromName') || 'From Name'}
+                  {t('settings.email.testRecipient') || 'Test Recipient Email'}
                 </label>
                 <input
-                  type="text"
-                  value={smtpSettings.smtp_from_name}
-                  onChange={(e) => setSMTPSettings({ ...smtpSettings, smtp_from_name: e.target.value })}
-                  placeholder="BamBuddy"
-                  className="w-full px-4 py-3 bg-bambu-dark-secondary border border-bambu-dark-tertiary rounded-lg text-white placeholder-bambu-gray focus:outline-none focus:ring-2 focus:ring-bambu-green/50 focus:border-bambu-green transition-colors"
+                  type="email"
+                  value={testEmail}
+                  onChange={(e) => setTestEmail(e.target.value)}
+                  placeholder="test@example.com"
+                  className={inputClasses}
                 />
               </div>
-            </div>
-
-            <div className="flex gap-2">
               <Button
-                onClick={handleSave}
-                disabled={saveMutation.isPending}
-                className="flex-1"
+                onClick={handleTest}
+                disabled={testMutation.isPending}
+                variant="secondary"
               >
-                {saveMutation.isPending ? (
+                {testMutation.isPending ? (
                   <>
                     <Loader2 className="w-4 h-4 animate-spin" />
-                    {t('settings.email.saving') || 'Saving...'}
+                    {t('settings.email.sending') || 'Sending...'}
                   </>
                 ) : (
-                  t('settings.email.save') || 'Save Settings'
+                  <>
+                    <Send className="w-4 h-4" />
+                    {t('settings.email.sendTest') || 'Send Test Email'}
+                  </>
                 )}
               </Button>
             </div>
-          </div>
-        </CardContent>
-      </Card>
-
-      {/* Test SMTP */}
-      <Card>
-        <CardHeader>
-          <h2 className="text-lg font-semibold text-white">
-            {t('settings.email.testConnection') || 'Test SMTP Connection'}
-          </h2>
-        </CardHeader>
-        <CardContent>
-          <div className="space-y-4">
-            <div>
-              <label className="block text-sm font-medium text-white mb-2">
-                {t('settings.email.testRecipient') || 'Test Recipient Email'}
-              </label>
-              <input
-                type="email"
-                value={testEmail}
-                onChange={(e) => setTestEmail(e.target.value)}
-                placeholder="test@example.com"
-                className="w-full px-4 py-3 bg-bambu-dark-secondary border border-bambu-dark-tertiary rounded-lg text-white placeholder-bambu-gray focus:outline-none focus:ring-2 focus:ring-bambu-green/50 focus:border-bambu-green transition-colors"
-              />
-            </div>
-            <Button
-              onClick={handleTest}
-              disabled={testMutation.isPending}
-              variant="secondary"
-            >
-              {testMutation.isPending ? (
-                <>
-                  <Loader2 className="w-4 h-4 animate-spin" />
-                  {t('settings.email.sending') || 'Sending...'}
-                </>
-              ) : (
-                <>
-                  <Send className="w-4 h-4" />
-                  {t('settings.email.sendTest') || 'Send Test Email'}
-                </>
-              )}
-            </Button>
-          </div>
-        </CardContent>
-      </Card>
+          </CardContent>
+        </Card>
+      </div>
 
     </div>
   );

+ 2 - 2
frontend/src/components/VirtualPrinterCard.tsx

@@ -31,7 +31,7 @@ export function VirtualPrinterCard({ printer, models }: VirtualPrinterCardProps)
   const queryClient = useQueryClient();
   const { showToast } = useToast();
 
-  const [expanded, setExpanded] = useState(false);
+  const [expanded, setExpanded] = useState(true);
   const [localEnabled, setLocalEnabled] = useState(printer.enabled);
   const [localName, setLocalName] = useState(printer.name);
   const [localAccessCode, setLocalAccessCode] = useState('');
@@ -418,7 +418,7 @@ export function VirtualPrinterCard({ printer, models }: VirtualPrinterCardProps)
                 {localRemoteInterfaceIp ? (
                   <span className="flex items-center gap-1 text-xs text-green-400"><Check className="w-3 h-3" /></span>
                 ) : (
-                  <span className="flex items-center gap-1 text-xs text-bambu-gray"><Info className="w-3 h-3" /></span>
+                  <span className="flex items-center gap-1 text-xs text-bambu-gray" title={t('virtualPrinter.remoteInterface.optional')}><Info className="w-3 h-3" /></span>
                 )}
               </div>
               <div className="relative">

+ 1 - 1
frontend/src/components/VirtualPrinterList.tsx

@@ -34,7 +34,7 @@ export function VirtualPrinterList() {
   return (
     <div className="space-y-4">
       {/* Top row - Setup Required (25%) + How it works (75%) */}
-      <div className="grid grid-cols-1 lg:grid-cols-4 gap-4 items-start">
+      <div className="grid grid-cols-1 lg:grid-cols-4 gap-4 items-stretch">
         <Card className="border-l-4 border-l-yellow-500">
           <CardContent className="py-3 px-4">
             <div className="flex items-start gap-2">

File diff suppressed because it is too large
+ 0 - 0
static/assets/index-nSSxwAwH.js


File diff suppressed because it is too large
+ 0 - 0
static/assets/index-pLsuHUG_.css


+ 2 - 2
static/index.html

@@ -23,8 +23,8 @@
 
     <!-- Splash screens for iOS -->
     <link rel="apple-touch-startup-image" href="/img/android-chrome-512x512.png" />
-    <script type="module" crossorigin src="/assets/index-Cwc85_YR.js"></script>
-    <link rel="stylesheet" crossorigin href="/assets/index-DPf6CLKV.css">
+    <script type="module" crossorigin src="/assets/index-nSSxwAwH.js"></script>
+    <link rel="stylesheet" crossorigin href="/assets/index-pLsuHUG_.css">
   </head>
   <body>
     <div id="root"></div>

Some files were not shown because too many files changed in this diff